Fibretrace Monet docs/Backend integration audit/BE ask

Dashboard — Backend API Gaps

Date: 2026-05-29
Author: Frontend team (Project Monet — apps/dashboard)
Recipient: Malcolm / Fibretrace Backend team
Source of truth: audit.json + README.md in this directory


Context

apps/dashboard is a fork of the Lovable.dev project fibre-trace-studio. Malcolm built the Lovable prototype using Supabase as a rapid-delivery scratchpad. The frontend team has ported the UI screen-by-screen and is routing every data call through an adapter layer (apiAdapter.ts) that translates Lovable’s supabase.from(table) calls to Fibretrace Laravel REST endpoints where they exist.

After an exhaustive cross-reference against the full canvas API inventory (~300 endpoints, 6 route files) and the SAT codebase, 14 feature groups have no Laravel equivalent. For each of these, the Lovable Supabase schema is the design spec — Malcolm already defined the shape for the MVP. This document hands those specs to BE so the team can implement them in Laravel. Priority is based on user-visible impact and how many dashboard pages are blocked.


Priority Summary Table

# Feature / Table group Dashboard consumers Why no endpoint exists Suggested endpoint shape
1 sessions CollectionDetail, SessionDetail, useVerifications Canvas GET /orders/:code/processes has a total field mismatch — missing session_code, conducted_at, fibre_detected, facility_id GET /sessions (company-scoped list) + GET /sessions/:id
2 verifications CollectionDetail, AddVerificationsModal, CollectionPreview, VerificationPreview Canvas orders/processes model has no “verification grouping” resource GET /orders/:order_code/verifications + CRUD mutations
3 audits — per-scan rows SessionDetail, AdminVerifications, useVerifications GET /user/audits returns process-level containers, not per-scan ft_id/result/notes rows GET /sessions/:id/audits
4 dashboardApi stats Home, ProducerDashboard Canvas dashboardApi.ts uses inconsistent base URL /api/dashboard — appears to be a scaffold; hooks may not map to real endpoints GET /api/dashboard/stats etc. — BE to confirm or define
5 companies — confidence fields useVerifications, useProgrammeHeatmap, useConfidence Canvas GET /public-companies returns only uid + name Add fields to GET /public-companies OR new GET /company/:uid/confidence
6 session_shares CollectionDetail, SessionDetail Canvas has POST /user/audits/:uid/send (one-way transfer) but no many-to-many share join POST /sessions/:id/shares + DELETE /sessions/:id/shares/:share_id
7 fibre_programmes (+ participants, producers, producer_facilities, reservations) AdminProgrammes, useProgrammes, useProgrammeHeatmap Entirely new BE domain — no /programs or /fibre-programmes endpoint anywhere in canvas BE to define/fibre-programmes CRUD + nested relationships
8 pigment_ids (+ pigment_orders, pigment_company_assignments) AdminPigmentIds, useProgrammes, SessionDetail Lovable-native fibre tracking tables — zero canvas equivalent BE to define/pigment-ids CRUD
9 production_records (+ production_claims, claim_sessions, production_files) FibreCreation, LogProductionModal, AdminFibreProduction No /production-records or /production-claims in any canvas inventory file BE to define/production-records CRUD
10 sliver_delivery_units (+ sdu_activity_logs) SDU, AdminSDU, useGuideSignals Canvas only has scanning_intensity_module flag in company metadata — no /sdu endpoint BE to define/sliver-delivery-units CRUD
11 evidence_requests (+ evidence_request_shares) EvidencePack, EvidenceRequestDetail, useEvidenceRequests Lovable-native evidence pack workflow — no /evidence-requests in canvas BE to define/evidence-requests CRUD
12 notification_preferences — mutations SettingsNotifications, useNotificationPrefs GET /service-tab covers template reads; no per-user preference upsert endpoint in canvas Already covered by PUT /api/v2/user (notification_emails field) — FE will handle, no new BE work needed here unless per-preference granularity is wanted
13 company_details AdminCompanies Canvas has POST /company-meta but different semantics — no direct company_details CRUD POST /companies, PUT /companies/:id, DELETE /companies/:id
14 company_users — mutations AdminUsers, AdminCompanies Canvas has useSetUserStatusMutation / useDeleteUserMutation but different semantics — no admin user-create POST /company-users (admin create) + confirm semantics of existing status/delete endpoints
15 facility_licenses AdminFacilities, AdminLicensing Admin-only licensing concept — no /facility-licenses in canvas BE to define/facility-licenses CRUD
16 onboarding_tasks AdminOnboarding Admin onboarding wizard — Lovable-only concept, no canvas equivalent BE to define/onboarding-tasks CRUD

Per-Gap Detail


1. sessions

Supabase tables: sessions

Why blocked: Canvas GET /orders/:orderCode/processes returns ProcessProcesList:

{ id, due_date, scan_count, auditor: {id, name}, manufacturer_process: {id, name} }

This is a total field mismatch — missing session_code, conducted_at, fibre_detected, facility_id, pigment_id, verification_id, auditor_name. Cannot map without fabrication.

Dashboard pages / hooks consuming this:

  • CollectionDetail.tsx — session list within a collection
  • SessionDetail.tsx — full session object
  • useVerifications.ts — session aggregation per collection
  • useGuideSignals.ts — facility-scoped session signals

Lovable Supabase shape (columns the dashboard reads):

sessions (
  id               uuid PRIMARY KEY,
  session_code     text NOT NULL,
  conducted_at     timestamptz NOT NULL,
  fibre_detected   text,           -- e.g. "15/20 scans detected"
  scan_count       int DEFAULT 0,
  facility_id      uuid REFERENCES facilities(id),
  verification_id  uuid REFERENCES verifications(id),
  production_record_id  uuid REFERENCES production_records(id) NULLABLE,
  auditor_name     text,
  pigment_id       uuid NULLABLE,
  deleted_at       timestamptz NULLABLE
)

Queries the dashboard performs:

// List — CollectionDetail.tsx
supabase.from("sessions")
  .select("id, session_code, conducted_at, fibre_detected, scan_count, facility_id, verification_id, production_record_id, auditor_name, pigment_id")
  .in("facility_id", myFacilityIds)
  .is("deleted_at", null)

// Detail — SessionDetail.tsx
supabase.from("sessions").select("*").eq("id", sessionId).single()

// Delete — CollectionDetail.tsx
supabase.from("sessions").delete().eq("id", sessionId)

Proposed REST endpoints (follow canvas orders/ conventions):

GET    /sessions
  Query params: facility_id?, verification_id?, page?, number_per_page?, sort?, sort_by?
  Response: { data: Session[], meta: { total, current_page, ... } }

GET    /sessions/:id
  Response: { data: Session }

DELETE /sessions/:id
  Response: 204

Note: BE team should decide: does “session” map to an enriched order process record, or is it a new concept? If the former, enrich GET /orders/:orderCode/processes response with the missing fields. If the latter, expose /sessions as a new resource.


2. verifications (groupings)

Supabase tables: verifications

Why blocked: Canvas treats an “order process” as the unit of work. Lovable introduces “verifications” as a named grouping that links sessions to a collection (order). There is no equivalent resource in any canvas endpoint.

Dashboard pages / hooks consuming this:

  • CollectionDetail.tsx, CreateCollectionFlow.tsx — list/create/reassign verifications
  • AddVerificationsModal.tsx — bulk assign verifications to a collection
  • CollectionPreview.tsx, VerificationPreview.tsx — read-only preview
  • useVerifications.ts, useCollections.ts

Lovable Supabase shape:

verifications (
  id            uuid PRIMARY KEY,
  name          text NOT NULL,
  status        text,           -- e.g. "active", "completed"
  collection_id uuid REFERENCES collections(id),   -- maps to order_code
  deleted_at    timestamptz NULLABLE
)

Queries the dashboard performs:

supabase.from("verifications")
  .select("id, name, status, collection_id")
  .in("collection_id", collectionIds)
  .is("deleted_at", null)

// Bulk reassign (AddVerificationsModal)
supabase.from("verifications")
  .update({ collection_id: newCollectionId })
  .in("id", verificationIds)

Proposed REST endpoints:

GET    /orders/:order_code/verifications
  Response: { data: [{id, name, status, collection_id}] }

POST   /orders/:order_code/verifications
  Body: { name: string }
  Response: { data: Verification }

PUT    /orders/:order_code/verifications/:id
  Body: { name?, status?, collection_id? }
  Response: { data: Verification }

DELETE /orders/:order_code/verifications/:id
  Response: 204

3. audits — per-scan rows

Supabase tables: audits

Why blocked (PARTIAL): GET /api/v1/sat-audits exists but returns one row per SAT audit submission with an embedded scans[] array. Lovable’s dashboard treats audits as per-scan rows with individual ft_id, blockchain_guid, result, notes, and a session_id FK. Canvas GET /user/audits returns process-level audit containers for the authenticated auditor’s assignment queue — wrong scope and shape.

The sat-audits endpoint is also listed as “partially implemented” in BE’s own notes. Please confirm whether it is live and production-ready before FE wires the dashboard.

Dashboard pages / hooks consuming this:

  • SessionDetail.tsx — per-scan rows within a session
  • AdminVerifications.tsx — admin audit list with soft-delete / edit
  • useVerifications.tsaudit_count per session + PO reference extraction

Lovable Supabase shape:

audits (
  id              uuid PRIMARY KEY,
  ft_id           text NULLABLE,          -- FibreTrace ID detected in this scan
  blockchain_guid text NULLABLE,
  result          text,                   -- "pass" | "fail" | "pending"
  notes           text NULLABLE,          -- contains "PO: XXX" pattern
  session_id      uuid REFERENCES sessions(id),
  created_at      timestamptz,
  deleted_at      timestamptz NULLABLE    -- soft delete
)

Queries the dashboard performs:

// Per-session scan list — SessionDetail.tsx
supabase.from("audits")
  .select("id, ft_id, blockchain_guid, result, notes, created_at")
  .eq("session_id", sessionId)

// Audit count + PO extraction — useVerifications.ts
supabase.from("audits")
  .select("id, session_id, notes")
  .in("session_id", sessionIds)
  .is("deleted_at", null)

// Admin mutations — AdminVerifications.tsx
supabase.from("audits").update({ deleted_at: now }).eq("id", id)   // soft-delete
supabase.from("audits").update({ deleted_at: null }).eq("id", id)  // restore
supabase.from("audits").update({ result, notes }).eq("id", id)     // edit

Proposed REST endpoints:

GET   /sessions/:id/audits
  Response: { data: [{id, ft_id, blockchain_guid, result, notes, session_id, created_at}] }

PATCH /sat-audits/:id
  Body: { result?, notes?, deleted_at? }
  Response: { data: Audit }

Note: If BE confirms that a SAT audit submission = a “session” and the scans[] items = “audit rows”, then the FE can denormalise the response client-side. BE should confirm the mapping explicitly so FE can finalise the adapter.


4. dashboardApi stats

Why blocked: Canvas dashboardApi.ts uses baseUrl: '/api/dashboard' with a Bearer token pulled directly from Redux state — inconsistent with all other canvas APIs that use the standard base URL. Canvas team notes indicate this file may be a scaffold/stub. The four hook names (useGetDashboardDataQuery, useGetDashboardStatsQuery, useGetRecentActivityQuery, useGetChartDataQuery) may not correspond to real endpoints.

Dashboard pages consuming this:

  • Home.tsx — summary stats (total scans, active collections, recent activity)
  • ProducerDashboard.tsx — producer-view stats

FE needs BE to either:

  1. Confirm the /api/dashboard endpoints are real and document request/response shapes; or
  2. Define new aggregate stats endpoints (e.g. GET /dashboard/stats) that return the home-page metrics, and FE will wire them.

Minimum data Home.tsx needs (based on Lovable UI):

{
  "total_scans": 1234,
  "active_collections": 56,
  "scanners_assigned": 7,
  "recent_activity": [
    { "id": "...", "type": "scan|collection|session", "description": "...", "created_at": "..." }
  ]
}

5. companies — confidence fields

Supabase tables: companies (additional columns)

Why blocked (PARTIAL): GET /public-companies exists and is wired. It returns only uid and name. The dashboard confidence UI (ConfidenceBadge, ConfidenceCard, useConfidence) reads confidence_score and confidence_rating per company — these are Lovable-only Supabase columns with zero BE source today.

Dashboard consumers: useVerifications.ts:72, useProgrammeHeatmap.ts:139, useConfidence.ts

Lovable Supabase shape (additional columns on companies):

confidence_score              numeric(5,2) NULLABLE,  -- 0-100 computed score
confidence_rating             text NULLABLE,           -- "Moderate" | "Good" | "Excellent"
confidence_summary            text,
confidence_drivers            jsonb,                   -- array of score driver objects
confidence_last_calculated_at timestamptz NULLABLE,
confidence_manual_override    boolean DEFAULT false,
confidence_override_reason    text,
confidence_updated_by         text,                    -- user uid
confidence_notes              text,
sharing_enabled               boolean DEFAULT false

Proposed approach (BE to choose one):

Option A — Add fields to existing GET /public-companies response:

GET /public-companies
  Response: { data: [{ uid, name, confidence_score, confidence_rating, sharing_enabled }] }

Option B — New endpoint per company:

GET /company/:uid/confidence
  Response: { data: { confidence_score, confidence_rating, confidence_summary,
                      confidence_drivers, confidence_last_calculated_at,
                      confidence_manual_override, confidence_override_reason,
                      confidence_notes, sharing_enabled } }

6. session_shares

Supabase tables: session_shares

Why blocked: Canvas has POST /user/audits/:uid/send (one-way audit transfer), but Lovable models session sharing as a many-to-many join — a company can receive read access to another company’s session. The semantics differ; BE decision required.

Dashboard consumers: CollectionDetail.tsx:211, SessionDetail.tsx

Lovable Supabase shape:

session_shares (
  id            uuid PRIMARY KEY,
  session_id    uuid REFERENCES sessions(id),
  shared_with   uuid REFERENCES companies(id),  -- receiving company
  shared_by     uuid REFERENCES companies(id),  -- sharing company
  created_at    timestamptz
)

Proposed REST endpoints:

GET    /sessions/:id/shares
  Response: { data: [{id, session_id, shared_with, shared_by, created_at}] }

POST   /sessions/:id/shares
  Body: { shared_with: string }   -- company uid
  Response: { data: SessionShare }

DELETE /sessions/:id/shares/:share_id
  Response: 204

7. fibre_programmes (and nested tables)

Supabase tables: fibre_programmes, programme_participants, programme_producers, programme_producer_facilities, programme_reservations

Why blocked: Entirely new BE domain. No /programs or /fibre-programmes endpoint exists in any of the 6 canvas inventory files. The MAPPERS["fibre_programmes"] block in apiAdapter.ts already has a // TODO Phase D comment pointing here.

Dashboard consumers: AdminProgrammes.tsx, useProgrammes.ts, useProgrammeHeatmap.ts

Lovable Supabase shapes (key columns):

fibre_programmes (
  id           uuid PRIMARY KEY,
  name         text NOT NULL,
  description  text,
  status       text,           -- "active" | "draft" | "completed"
  owner_id     uuid REFERENCES companies(id),
  start_date   date NULLABLE,
  end_date     date NULLABLE,
  created_at   timestamptz
)

programme_participants (
  id             uuid PRIMARY KEY,
  programme_id   uuid REFERENCES fibre_programmes(id),
  company_id     uuid REFERENCES companies(id),
  role           text,           -- "producer" | "verifier" | "auditor"
  joined_at      timestamptz
)

programme_producers (
  id             uuid PRIMARY KEY,
  programme_id   uuid REFERENCES fibre_programmes(id),
  company_id     uuid REFERENCES companies(id)
)

programme_producer_facilities (
  id             uuid PRIMARY KEY,
  producer_id    uuid REFERENCES programme_producers(id),
  facility_id    uuid REFERENCES facilities(id)
)

programme_reservations (
  id             uuid PRIMARY KEY,
  programme_id   uuid REFERENCES fibre_programmes(id),
  quantity       int,
  reserved_by    uuid REFERENCES companies(id),
  reserved_at    timestamptz
)

Minimum endpoints needed (BE to define full spec):

GET  /fibre-programmes           -- company-scoped list
POST /fibre-programmes           -- create
GET  /fibre-programmes/:id       -- detail with nested participants/producers
PUT  /fibre-programmes/:id       -- update
GET  /fibre-programmes/:id/participants
POST /fibre-programmes/:id/participants
GET  /fibre-programmes/:id/producers

8. pigment_ids (and related tables)

Supabase tables: pigment_ids, pigment_orders, pigment_company_assignments

Why blocked: Lovable-native fibre/pigment tracking tables. Zero canvas equivalent found in any inventory file.

Dashboard consumers: AdminPigmentIds.tsx, useProgrammes.ts, useProgrammeHeatmap.ts, SessionDetail.tsx:134

Lovable Supabase shapes (key columns):

pigment_ids (
  id            uuid PRIMARY KEY,
  code          text NOT NULL UNIQUE,
  description   text NULLABLE,
  status        text,   -- "available" | "assigned" | "depleted"
  created_at    timestamptz
)

pigment_orders (
  id            uuid PRIMARY KEY,
  pigment_id    uuid REFERENCES pigment_ids(id),
  quantity      int,
  ordered_by    uuid REFERENCES companies(id),
  ordered_at    timestamptz
)

pigment_company_assignments (
  id            uuid PRIMARY KEY,
  pigment_id    uuid REFERENCES pigment_ids(id),
  company_id    uuid REFERENCES companies(id),
  assigned_at   timestamptz
)

Minimum endpoints needed (BE to define full spec):

GET  /pigment-ids
POST /pigment-ids
GET  /pigment-ids/:id
PUT  /pigment-ids/:id
POST /pigment-ids/:id/assign    body: { company_id }
GET  /pigment-orders
POST /pigment-orders

9. production_records (and related tables)

Supabase tables: production_records, production_claims, production_claim_sessions, production_files

Why blocked: Fibre production / SDU claim flow. No /production-records or /production-claims endpoint in any canvas inventory file.

Dashboard consumers: FibreCreation.tsx, LogProductionModal.tsx, useGuideSignals.ts, usePersona.ts, AdminFibreProduction.tsx

Lovable Supabase shapes (key columns):

production_records (
  id              uuid PRIMARY KEY,
  company_id      uuid REFERENCES companies(id),
  facility_id     uuid REFERENCES facilities(id),
  quantity        numeric,
  unit            text,           -- "kg" | "m" | etc.
  batch_number    text NULLABLE,
  produced_at     timestamptz,
  created_at      timestamptz
)

production_claims (
  id                  uuid PRIMARY KEY,
  production_record_id uuid REFERENCES production_records(id),
  claimed_by          uuid REFERENCES companies(id),
  quantity_claimed    numeric,
  status              text,   -- "pending" | "approved" | "rejected"
  claimed_at          timestamptz
)

production_claim_sessions (
  id          uuid PRIMARY KEY,
  claim_id    uuid REFERENCES production_claims(id),
  session_id  uuid REFERENCES sessions(id)
)

production_files (
  id                   uuid PRIMARY KEY,
  production_record_id uuid REFERENCES production_records(id),
  file_url             text,
  file_type            text,
  uploaded_at          timestamptz
)

Minimum endpoints needed (BE to define full spec):

GET  /production-records
POST /production-records
GET  /production-records/:id
GET  /production-claims
POST /production-claims            body: { production_record_id, quantity_claimed }
PUT  /production-claims/:id        body: { status: "approved" | "rejected" }

10. sliver_delivery_units (SDU)

Supabase tables: sliver_delivery_units, sdu_activity_logs

Why blocked: Canvas only stores a scanning_intensity_module boolean flag in company metadata. There is no /sliver-delivery-units or /sdu endpoint anywhere in the canvas inventory.

Dashboard consumers: SDU.tsx, AdminSDU.tsx, useGuideSignals.ts

Lovable Supabase shapes (key columns):

sliver_delivery_units (
  id               uuid PRIMARY KEY,
  company_id       uuid REFERENCES companies(id),
  sdu_code         text NOT NULL UNIQUE,
  status           text,    -- "active" | "inactive" | "deployed"
  facility_id      uuid REFERENCES facilities(id) NULLABLE,
  created_at       timestamptz
)

sdu_activity_logs (
  id         uuid PRIMARY KEY,
  sdu_id     uuid REFERENCES sliver_delivery_units(id),
  action     text,           -- "created" | "deployed" | "recalled"
  performed_by uuid REFERENCES users(id),
  performed_at timestamptz,
  notes      text NULLABLE
)

Minimum endpoints needed (BE to define full spec):

GET  /sliver-delivery-units
POST /sliver-delivery-units
GET  /sliver-delivery-units/:id
PUT  /sliver-delivery-units/:id
GET  /sliver-delivery-units/:id/activity-logs

11. evidence_requests

Supabase tables: evidence_requests, evidence_request_shares

Why blocked: Lovable-native evidence pack workflow. No /evidence-requests endpoint in any canvas inventory file.

Dashboard consumers: useEvidenceRequests.ts, EvidencePack.tsx, EvidenceRequestDetail.tsx

Lovable Supabase shapes (key columns):

evidence_requests (
  id               uuid PRIMARY KEY,
  requester_id     uuid REFERENCES companies(id),
  target_company_id uuid REFERENCES companies(id),
  status           text,   -- "pending" | "fulfilled" | "rejected"
  message          text NULLABLE,
  created_at       timestamptz,
  fulfilled_at     timestamptz NULLABLE
)

evidence_request_shares (
  id                   uuid PRIMARY KEY,
  evidence_request_id  uuid REFERENCES evidence_requests(id),
  session_id           uuid REFERENCES sessions(id),
  shared_at            timestamptz
)

Minimum endpoints needed (BE to define full spec):

GET  /evidence-requests           -- scoped to auth company (as requester or target)
POST /evidence-requests           body: { target_company_id, message? }
GET  /evidence-requests/:id
PUT  /evidence-requests/:id       body: { status: "fulfilled" | "rejected" }
POST /evidence-requests/:id/shares  body: { session_id }

12. notification_preferences — mutations

Why this is likely already handled:

GET /user already returns notification_emails: Record<OPT_IN_KEY, boolean> on the user object (confirmed in both canvas userApi.ts:55-60 and SAT auth.ts:27).

PUT /api/v2/user (canvas userApi.ts:70-74) accepts the full user object spread with notification_emails overridden — this is the write path for toggling notification preferences.

FE assessment: No new endpoint is needed. FE will wire PUT /api/v2/user directly.

The only ask: Confirm that PUT /api/v2/user is accessible with the dashboard’s Bearer partner_token (same auth as all other dashboard endpoints). Canvas uses Bearer access_token. If there is a token-type restriction, BE should expose a lighter PATCH /user/notification-emails endpoint instead:

PATCH /user/notification-emails
  Body: { OPT_IN_INVOICE_NOTIFICATION?: boolean, OPT_IN_SCANNER_NOTIFICATION?: boolean, ... }
  Response: { data: { notification_emails: Record<string, boolean> } }

13. company_details / admin company CRUD

Supabase tables: companies (admin mutations)

Why blocked: Canvas GET /public-companies exists (wired). Admin CRUD (create, update, delete company records) has no canvas equivalent. AdminCompanies.tsx performs direct Supabase insert/update/delete on the companies table.

Dashboard consumers: AdminCompanies.tsx (admin-only route /admin/companies)

Proposed REST endpoints:

POST   /companies
  Body: { name: string }
  Response: { data: { id: string, name: string } }

PUT    /companies/:id
  Body: { name?, confidence_manual_override?, confidence_override_reason?,
          confidence_notes?, sharing_enabled? }
  Response: { data: Company }

DELETE /companies/:id
  Response: 204

14. company_users — admin mutations

Supabase tables: company_users

Why blocked: Canvas has useSetUserStatusMutation, useDeleteUserMutation, and useRestoreUserMutation but with different semantics than direct CRUD. There is no admin-style user-create endpoint in canvas.

Dashboard consumers: AdminUsers.tsx, AdminCompanies.tsx

Operations the dashboard admin UI performs:

// Create user in a company
supabase.from("company_users").insert({ user_id, company_id, role })

// Update user role / status
supabase.from("company_users").update({ role, status }).eq("id", id)

// Delete user from company
supabase.from("company_users").delete().eq("id", id)

Ask: Clarify whether the existing canvas PUT /user/set-status and DELETE /user/:uid endpoints cover these admin operations, or if a separate admin user-management endpoint is needed. If the existing endpoints have company-scope restrictions, expose:

POST   /company/users              body: { email, role }  (invite + assign)
PUT    /company/users/:id          body: { role?, status? }
DELETE /company/users/:id

15. facility_licenses

Supabase tables: facility_licenses

Why blocked: Admin-only licensing concept. No /facility-licenses endpoint anywhere in canvas.

Dashboard consumers: AdminFacilities.tsx, AdminLicensing.tsx

Lovable Supabase shape (key columns):

facility_licenses (
  id             uuid PRIMARY KEY,
  facility_id    uuid REFERENCES facilities(id),
  license_type   text,           -- e.g. "organic", "fairtrade", "gots"
  license_number text,
  issued_at      date NULLABLE,
  expires_at     date NULLABLE,
  status         text,           -- "active" | "expired" | "pending"
  created_at     timestamptz
)

Minimum endpoints needed (BE to define full spec):

GET  /facilities/:id/licenses
POST /facilities/:id/licenses     body: { license_type, license_number, issued_at?, expires_at? }
PUT  /facilities/:id/licenses/:license_id
DELETE /facilities/:id/licenses/:license_id

16. onboarding_tasks

Supabase tables: onboarding_tasks

Why blocked: Lovable-only admin onboarding wizard concept. No /onboarding-tasks endpoint in canvas.

Dashboard consumers: AdminOnboarding.tsx

Lovable Supabase shape (key columns):

onboarding_tasks (
  id           uuid PRIMARY KEY,
  company_id   uuid REFERENCES companies(id),
  task_type    text,   -- e.g. "setup_scanner", "invite_team", "first_audit"
  completed    boolean DEFAULT false,
  completed_at timestamptz NULLABLE,
  created_at   timestamptz
)

Minimum endpoints needed (BE to define full spec):

GET  /onboarding-tasks            -- company-scoped
POST /onboarding-tasks/:id/complete

scanner_facility_assignments (PARTIAL — BE gap within an existing feature)

This is documented as part of the scanners PARTIAL contract but is worth calling out separately.

Why blocked: Canvas PUT /company/scanner/transfer/:id transfers a scanner to a user_id, not a facility_id. The Lovable UI shows a “Facility” column on the scanner list — this requires a scanner-to-facility assignment join that has no canvas equivalent.

Lovable Supabase shape:

scanner_facility_assignments (
  id           uuid PRIMARY KEY,
  scanner_id   text REFERENCES scanners(id),
  facility_id  uuid REFERENCES facilities(id),
  assigned_at  timestamptz
)

Proposed REST endpoints:

GET    /company/scanner/assignments
  Response: [{ id, scanner_id, facility_id, assigned_at }]

POST   /company/scanner/assignments
  Body: { scanner_id: string, facility_id: string }
  Response: { data: ScannerFacilityAssignment }

DELETE /company/scanner/assignments/:id
  Response: 204

Settings — field-level GUI-parity gaps (Lovable port, 2026-05-29 PM)

These are NOT new feature domains — they are individual fields the Lovable MVP renders on existing Settings screens that the current Fibretrace endpoints do not return. Each is a small additive field on an endpoint that already exists. The dashboard currently shows these rows empty (no fabricated data) pending BE. Listed here so they batch with the gaps above.

# Field Dashboard consumer Existing endpoint to extend Suggested shape
S1 billing_period SettingsBilling (Billing period row) GET /company (or subscription/billing payload) billing_period: "monthly" | "annual"
S2 payment_method SettingsBilling (Payment method row) GET /company (or billing payload) payment_method: { brand: string, last4: string, exp?: string }
S3 billing_address SettingsBilling (Billing address row) GET /company (or billing payload) billing_address: { line1, line2?, city, state?, postcode, country }
S4 invoice pdf_url SettingsBilling invoice actions (View / Download) GET /company/invoices items add pdf_url: string per invoice row so View/Download can link
S5 supply_chain_tier SettingsCompany (Supply chain tier dropdown) GET /company supply_chain_tier: string (enum TBD by BE)

Not pursued (security anti-pattern): SettingsApiKeys eye-toggle to reveal a full API key on the list. BE only returns key_preview; the full key is shown once at creation. Revealing full keys on a list endpoint is a deliberate security boundary — FE will NOT request this; the eye-toggle stays disabled/hidden.


Already EXISTS — No BE work needed

The following items have confirmed Laravel REST equivalents and are either already wired or in-progress on the FE side. BE does not need to build anything new for these.

Feature Endpoint Status
Invoices list GET /company/invoices Wired and live
Invoice detail GET /company/invoices/:code FE adding to slice
Notification preferences GET /user + PUT /api/v2/user FE wiring
Public companies list GET /public-companies Wired and live
Collections (orders) list GET /orders Wired and live
Collection detail GET /orders/:orderCode FE adding to slice
Scanners list GET /company/scanners Wired and live
Scanner create/update/transfer POST /company/scanner, POST /post-auditor-update-scanner, PUT /company/scanner/transfer/:id FE adding mutations
SAT audits (list) GET /sat-audits Wired — awaiting BE confirmation it is live
Facilities list GET /company/processes Wired and live
Notification templates (read) GET /service-tab FE handling via static mapper

Machine-readable source: audit.json in this directory. Frontend adapter layer: apps/dashboard/src/lib/apiAdapter.ts.