Service Marketplace
Bounded context for sourcing, profiling, and dispatching independent service providers. Active api-v1 implementation persists Provider, Professional, ServiceArea, and City; marketplace profile overlays remain stubbed.
Overview
Service Marketplace owns provider sourcing in apps/api-v1/src/modules/service-marketplace. It replaces the deprecated name Labor Marketplace. A Provider is a marketplace projection of a service-providing organization, created from a source URL or entered manually. Future party-directory integration links that projection to a canonical Organization.
The module follows api-v1 hexagonal structure with Airtable as the persistence adapter for Provider, Professional, ServiceArea, and City. ProviderProfile, ServiceOffering, LicenseCredential, and InsuranceCredential exist as TypeScript domain types and events, but their stores are in-memory placeholders and the HTTP detail routes return 501. Do not treat those four as persisted Airtable behavior.
Implementation Status
Status: Implemented in api-v1
The future api-v2 Prisma module is the long-term target for first-class profile, offering, and credential persistence. This page documents the active api-v1 implementation first.
Aggregates and supporting types
| Concept | Persistence today | Badge |
|---|---|---|
Provider | Airtable providers | Implemented in api-v1 |
Professional | Airtable professionals (HTTP register returns 501) | Implemented in api-v1 |
ServiceArea | Airtable service areas | Implemented in api-v1 |
City | Airtable cities | Implemented in api-v1 |
ProviderProfile, ServiceOffering, LicenseCredential, InsuranceCredential | In-memory pending repositories; HTTP 501 | Stubbed / Planned for api-v2 |
Provider is the aggregate root. ProviderProfileId is branded as the same identity as ProviderId.
Create from URL
Create Provider From URL is the active scrape-and-persist path. CreateProviderFromUrlUseCase calls DeterministicProviderScraperAdapter, which loads a module-local Playwright scraper (currently Houzz) — not the shared-scraping catalog service and not BrightData. After persist, the use case optionally calls party-directory ForRegisteringOrganizations.findOrCreateByDomain and links the provider via Provider.linkToOrganization.
There is no nested provider-ingestion System: the scrape path does not use shared-scraping or BrightData, so a product-ingestion-style System would invent machinery that is not there.
Primary Code References
- Module root:
apps/api-v1/src/modules/service-marketplace - Glossary:
apps/api-v1/src/modules/service-marketplace/GLOSSARY.md - Composition:
apps/api-v1/src/modules/service-marketplace/composition/service-marketplace.composition.ts - REST:
apps/api-v1/src/modules/service-marketplace/adapters/inbound/rest - Airtable base id:
appf6O7p7uadUeS4o(AIRTABLE_SERVICE_MARKETPLACE_BASE_ID)
HTTP surface
Canonical mount: /api/v1/service-marketplace.
| Method | Path | Behavior |
|---|---|---|
POST | /providers/from-url | Create-from-URL scrape (202) |
DELETE | /providers/:providerId | Soft-delete provider |
POST / DELETE | /providers/:providerId/service-areas | Add or remove coverage |
POST | /providers/:providerId/profile (and offerings, licenses, insurance, professionals) | 501 placeholders |
POST | /contractors/... | Same router as /providers (legacy alias) |
Also mounted: /api/v1/labor-marketplace and /api/v1/service-marketplace/contractor-from-url legacy aliases. There is no colocated *.openapi.ts for this module yet — do not invent OpenAPI operation ids.
Documentation Links
- api-v1 service:
/docs/services/api-v1 - Party Directory:
/docs/services/party-directory - Ubiquitous language:
/docs/domains/service-marketplace/language - Backend platform:
/docs/systems/backend-platform - ADRs:
docs/adr/README.md
Open Questions
- When first-class
ProviderProfile/ offering / credential persistence lands in api-v2, which Airtable tables (if any) remain a read model versus a write model? - Whether provider scrape should eventually share the
shared-scraping/ BrightData path used by product-marketplace. Today it does not.