Document Index

01
Market Overview
TAM, kotitalousvähennys mechanics, Finnish consumer behaviour, competitive landscape
Strategy
02
Competitor Benchmarking
TaskRabbit, Thumbtack, Helpling, Bark — model comparison and lessons for Homora
Strategy
03
Business Model Analysis
Transaction fee vs lead fee vs subscription, pricing model, revenue projections
Strategy
04
MVP Engineering Spec
All screens, user flows, data model, API routes, 6-week build sequence
Engineering
05
Product Roadmap
Phase 1–3 milestones, feature checklist by week, Mahdi vs Abbas ownership split
Strategy
06
User Stories
P0/P1/P2 stories with acceptance criteria for customer, provider, and admin
Engineering
07
Database Schema
Full Supabase/PostgreSQL schema with RLS policies, triggers, and indexes
Engineering
08
API Spec
REST endpoints with request/response examples and error codes
Engineering
09
UX Flows
Every screen with states, edge cases, and key UX decisions
Engineering
10
PRD
Problem statement, target users, scope in/out, feature priorities, success metrics
Requirements
11
Business Rules
Commission, cancellation, payout, Y-tunnus, tax receipt, review rules
Requirements
12
Non-Functional Requirements
GDPR, security, performance, browser support, backup/recovery
Requirements
1

Market Overview

🎯
The opportunity in one sentence: Finland has 2.7 million households, no dominant home services marketplace, a government-subsidized demand driver (kotitalousvähennys), and a large informal economy waiting to be formalized.
€500–800M
Total Addressable Market (Finland)
45%
Tax deduction rate on labor costs
€2,250
Max deduction per person/year

Market Size by Segment

SegmentAnnual Value (Finland)
Professional home cleaning€200–300M
Handyman / home repair€150–200M
Moving services€80–120M
Garden / outdoor€50–80M
Other household services€100–150M
Total Addressable Market€500–800M

Kotitalousvähennys — The Built-In Demand Subsidy

A direct income tax deduction (not a taxable income reduction — a direct deduction from tax owed) for work performed at a Finnish household. No global competitor has built around it.

ParameterValue
Deduction rate45% of the labor cost
Annual cap per person€2,250
Per household (both spouses)€4,500
Provider requirementMust have Finnish Y-tunnus and be tax-registered
Where to claimAnnual tax return at vero.fi → Kotitalousvähennys
💡
Example: You pay €1,000 for cleaning labor → 45% = €450 deducted from your tax bill → effective cost: €550. A household spending €5,000/year on home services effectively pays €2,750 after deduction.
Informal market (cash)Homora platform
No tax receiptAutomatic itemized receipt
Customer loses deductionCustomer saves 45% on labor
Provider avoids taxesProvider legally registered (Y-tunnus)
No accountabilityReviews, payment protection, recourse

Current State — HOMORA Service Oy (Feb 2026)

SignalValue
Legal entityHOMORA Service Oy, Helsinki · Mäntytie 21, 00270 Helsinki
Services liveCleaning, laundry & ironing, carpet cleaning, handyman, moving, sewing
Completed jobs120+ cleanings · 80+ laundry jobs
On-time rate98%
Customer satisfaction95%
Current techWordPress contact form — no real marketplace infrastructure
🚀
Cold start advantage: Abbas already has providers, customers, and operational knowledge. The platform build replaces a contact form with a real marketplace. Supply-side cold start problem is already solved.
2

Competitor Analysis

TaskRabbit

IKEA-acquired · US + 9 countries · Founded 2008
~25–30%
blended take rate

True marketplace. Client browses profiles, books instantly. Mandatory background checks. 15% client fee + 15% provider fee.

Instant booking Background checks IKEA integration

Thumbtack

$3.2B valuation · US only · Founded 2008
Lead fees
$5–150 per lead

Lead-gen marketplace. Pros pay per lead regardless of conversion. SEO dominance ("painter near me"). No transaction fee.

Provider resentment risk SEO leader

Helpling

Rocket Internet · DACH, Benelux, FR, UK · Founded 2014
~15–25%
take rate (marketplace era)

Started managed (set prices, dispatched cleaners), pivoted to marketplace. Background checks + liability insurance. Never launched in Finland.

Recurring bookings Gap: not in Finland

Bark.com

Profitable, bootstrapped · UK + English · Founded 2014
Credit model
£1–3 per credit

Zero friction for clients (no account required). Pros buy credits to respond to leads. High top-of-funnel but low trust infrastructure.

Low friction Minimal vetting

Feature Matrix — Where Homora Wins

FeatureTaskRabbitThumbtackHelplingBarkHomora
Instant bookingPartial
Integrated paymentNewer
Provider verificationOptional✅ Y-tunnus
Reviews & ratings
Tax receipts✅ AUTO-GENERATED
Available in Finland✅ Helsinki-first

Key Lessons Applied to Homora

#LessonHow Homora applies it
1Start with one service, one cityCleaning in Helsinki metro. Handyman in Phase 2.
2Avoid the managed modelTrue marketplace — self-employed providers (toiminimi) with Y-tunnus. No worker classification risk.
3Charge the transaction, not the lead20% commission on completed bookings. No lead fees. Aligned incentives.
4Trust is the conversion unlockY-tunnus verification + ID check + reviews. In Finland, trust is not optional.
5Tax receipt = Finnish moatAuto-generated kotitalousvähennys PDF on every completed booking. No competitor does this.
6Recurring bookings drive LTVRecurring booking mechanics in Phase 2 (weekly/bi-weekly cleaning).
3

Business Model

Chosen model: Transaction Fee (TaskRabbit model) — 20% commission on each completed booking, deducted from provider payout. No upfront cost to join. Aligns Homora's success with the provider's success.

Commission Structure — MVP

💶
Customer books 3h × €25/hr = €75.00 total
Provider receives: €75 × 80% = €60.00
Homora revenue: €75 × 20% = €15.00
Stripe fee (~1.4% + €0.25): ~€1.30 absorbed by Homora
Homora net: ~€13.70
PlatformBlended take ratevs Homora
Handy30–40%Much higher
TaskRabbit25–30%Higher
Homora20%Our rate
Helpling15–25%In range

Revenue Projections (Year 1, Helsinki)

MilestoneMonthly bookingsMonthly GMVMonthly revenue
Launch100€12,000€2,400
3 months300€36,000€7,200
6 months600€72,000€14,400
12 months1,500€180,000€36,000

The Kotitalousvähennys Pricing Angle

This is a pricing strategy, not just a feature. A service that costs €100 "feels" like €55 to a qualifying Finnish household after the tax deduction.

Standard framingHomora framing
"Cleaning service, €100""Cleaning service, €100. Your actual cost: €55"
Cleaner charges €30/hr informallyCleaner charges €40/hr on Homora — client pays effectively €22/hr after deduction

Phase 2 Revenue Additions

Revenue streamDetailsTimeline
Provider subscription"Homora Pro" €49/month — reduced fees (8%), premium placement, analyticsMonth 7+
Featured listingsProviders pay for top-of-search placementMonth 7+
Insurance add-on€2–5/booking, Finnish insurer partnership, platform takes a cutMonth 10+
B2B channelCleaning contracts for property managers, Airbnb hosts, small businessesMonth 10+
4

Product Roadmap

🗺️
Philosophy: Build for the next milestone, not the eventual product. Each phase must prove a specific hypothesis before unlocking the next phase.

Phase 1 — Prove the Loop Weeks 1–8

Hypothesis: Finnish customers will book a verified cleaner through Homora and pay online · Helsinki only · Cleaning only

WEEK 1–2: FOUNDATION

  • Supabase setup — auth, tables, RLS policies
  • Next.js scaffolding (App Router, Tailwind)
  • Provider registration form
  • Admin panel skeleton

WEEK 3–4: CORE FLOWS

  • Customer homepage + provider search
  • Provider profile + booking flow
  • Booking confirmation + notifications
  • Availability calendar (provider)

WEEK 5–6: MONEY & TRUST

  • Stripe PaymentIntent integration
  • Provider accept/decline (2h window)
  • Stripe Connect payouts
  • Tax receipt PDF auto-generation

WEEK 7–8: POLISH & LAUNCH

  • Admin: Y-tunnus review, dispute handling
  • Email notifications (Resend)
  • Mobile-responsive sweep
  • Provider onboarding (Abbas: 30 providers)
Done when: 30+ verified providers active · 100+ completed bookings · Repeat booking rate >30% · Zero critical payment disputes

Phase 2 — Grow the Core Months 3–6

Hypothesis: Recurring bookings drive LTV and provider retention · Helsinki + Espoo/Vantaa · Cleaning + Handyman
  • Recurring booking management (weekly / bi-weekly / monthly)
  • Automated Y-tunnus verification via Finnish Business Information System API
  • Handyman category — quote-based flow, portfolio photos
  • iOS + Android apps (React Native or PWA + Capacitor)
  • Provider earnings analytics dashboard
Done when: 40%+ of bookings are recurring · Handyman active · Monthly GMV >€50,000

Phase 3 — Expand & Monetize Months 7–12

Hypothesis: Platform can expand geographically and add revenue layers · Tampere, Turku · All categories
  • City-specific landing pages (SEO) + Tampere/Turku launch
  • "Homora Pro" provider subscription — €49/month, 8% commission
  • B2B channel — property managers, Airbnb hosts
  • Insurance add-on partnership
  • Smart matching + dynamic pricing suggestions
Done when: 3+ cities · Monthly GMV >€150,000 · Provider subscription MRR >€5,000

Mahdi vs. Abbas — Ownership Split

AreaMahdi (Tech)Abbas (Business)
Platform development✅ All of it
Provider onboarding (first 30)✅ Recruit manually
Customer acquisition✅ Marketing, channels
Tax receipt format✅ Builds✅ Confirms with accountant
Pricing decisionsAdvises✅ Owns
Category expansion timingAdvises on buildability✅ Decides based on demand
Partnership deals (IKEA, insurers)✅ Owns
5

Product Requirements (PRD)

MVP Scope — In

CategoriesCleaning, Handyman, Laundry, Moving, Carpet Cleaning
GeographyHelsinki metro (Helsinki, Espoo, Vantaa, Kauniainen)
Provider req.Valid Finnish Y-tunnus (mandatory)
Booking modelFixed hourly rate. No bidding or quotes.
PaymentCard-only via Stripe. Held until job done.
PayoutsWeekly, Monday, Finnish IBAN
Tax receiptsAuto-generated PDF, emailed to customer
PlatformWeb (mobile-responsive). No native app.

MVP Scope — Out

Native iOS / Android appPhase 2
Swedish languagePhase 2
Cities outside Helsinki metroPhase 3
Background check APIManual review
In-app chatPhone revealed after booking
Subscription plansPhase 2
Real-time GPS trackingNot needed
Materials cost handlingLabor only

Success Metrics — 3 Months Post-Launch

MetricTarget
Verified providers live20–30
Completed bookings100+
Monthly GMV€8,000–15,000
Tax receipts generated100% of completed bookings
Repeat booking rate>25%
Provider response rate (2h window)>85%
Platform uptime>99.5%
Customer NPS>40

Open Questions for Abbas

1How many providers can Abbas onboard on day 1? (Need minimum 5 to look credible)
2Do all current providers have Y-tunnus? (Mandatory — no Y-tunnus = not listed)
3What are typical hourly rates? (Calibrate the price display and tax calculator)
4Who handles customer support at launch? (Abbas solo or shared email?)
5Target launch date? (Drives the 6-week build countdown)
6Does HOMORA Service Oy have a Stripe account already?
7Who absorbs Stripe fees — built into commission or passed to customer?
6

Business Rules

⚖️
These are the exact rules the system must enforce. Every rule has a corresponding enforcement point in code or admin process.

Provider Eligibility

Y-tunnus
Required. Format: 7 digits + dash + 1 check digit (e.g. 1234567-8). No Y-tunnus = not listed.
Stripe Connect
Must complete Stripe Connect onboarding (Finnish IBAN) before going live. Payout impossible otherwise.
Admin approval
All providers start as pending. Searchable only after admin sets approved.
Hourly rate range
Min: €15/hr · Max: €100/hr (enforced at onboarding)

Booking Rules

Advance window
Minimum 24h notice. Maximum 60 days. No same-day booking at MVP.
Response window
Provider has 2 hours to accept or decline. Auto-decline after 2h. Customer not charged.
Address reveal
Provider sees neighborhood only before accepting. Full address revealed only after acceptance.
Double-booking
Once a slot is confirmed, locked for all other customers. Enforced at database level.

Cancellation Policy

WhoWhenOutcome
Customer>24h before startFull refund · No fee
Customer≤24h before start50% charged · 50% refunded
CustomerAfter job startedNo refund
ProviderAny time before jobFull refund to customer. Cancellation count tracked. 3 in 30 days → admin review.
Auto-declineAfter 2h no responseFull refund · No provider penalty (first time)

Payout Rules

Trigger
Provider marks job as completed. Admin can also mark complete.
Schedule
Weekly batch, every Monday at 10:00 Helsinki time for jobs completed prior week.
Hold period
Funds released to provider 7 days after job completion (Stripe standard).
Method
Stripe Connect → provider's Finnish IBAN.

Tax Receipt Rules (Finnish Law)

Trigger
Auto-generated when booking status → completed
Delivery
Emailed to customer within 5 minutes. Downloadable from "My Bookings" at any time.
Retention
Stored in Supabase Storage for minimum 10 years (Finnish accounting law)
Required fields
Provider Y-tunnus · Provider registered business name · Service date · Customer's address · Labor hours × rate · Total labor cost · Deductible amount (45%) · Homora's Y-tunnus
Deductible amount
labor_cost × 0.45. Shown as estimate during booking; locked on receipt after completion.
7

Non-Functional Requirements

Performance Targets

Page load (LCP)<2.5s on 4G mobile
Provider list API<500ms
Booking creation<1s (includes Stripe)
PDF generation<30s (async, emailed)
Platform uptime>99.5% monthly

Security Requirements

AuthSupabase Auth (JWTs, httpOnly cookies)
AuthorizationRow Level Security on all tables
Payment dataNever stored on Homora servers (Stripe PCI-DSS L1)
Provider docsPrivate Supabase Storage, signed URLs, admin-only
HTTPSEnforced by Vercel. No HTTP fallback.

GDPR Compliance (Finnish tietosuoja)

DataPurposeRetention
Customer name, email, phoneAccount, notificationsUntil deletion request
Customer home addressBooking fulfillment10 years (tax records)
Provider Y-tunnusTax receipt, legal compliance10 years
Provider ID documentIdentity verification2 years after decision
Booking history + receiptsTax receipts, dispute resolution10 years (Finnish accounting law)
IP addresses / session logsSecurity, fraud prevention90 days

Browser & Device Support

PlatformSupportNotes
Chrome (latest 2)Full
Safari (iOS 15+)FullPrimary mobile browser in Finland
Firefox (latest 2)Full
Edge (latest 2)Full
iOS Safari (iPhone SE+)FullMin viewport 375px
IE11Not supported
8

MVP Engineering Spec

Tech Stack

LayerChoiceWhy
FrontendNext.js (App Router)Fast build, SEO-friendly, React ecosystem
UITailwind + shadcn/uiShip fast, accessible, professional
Backend / DBSupabase (Postgres + Auth + Storage)One service for everything, generous free tier
PaymentsStripe (PaymentIntents + Connect)Standard for EU, handles provider payouts
EmailResendSimple API, great deliverability
PDFReact-pdfTax receipt generation in-process
HostingVercel (frontend) + Supabase (backend)Both have generous free tiers, auto-scaling
MapsGoogle Maps Places APIAddress autocomplete + geocoding

6-Week Build Sequence

Week 1 — Foundation

Supabase setup, auth tables, RLS, Next.js scaffold, provider registration form, admin skeleton

Week 2 — Provider Side

Provider profile (public), provider dashboard, Stripe Connect onboarding, availability calendar

Week 3 — Customer Side

Homepage + provider list/search, provider profile + "Book now" flow, booking form, price breakdown with kotitalousvähennys estimate

Week 4 — Payments

Stripe PaymentIntent at booking, booking status state machine, provider accept/decline (2h auto-decline), email notifications via Resend

Week 5 — Post-Job

Job completion flow, Stripe payout trigger, tax receipt PDF generation (React-pdf), email receipt, review prompt

Week 6 — Polish & Launch

Y-tunnus validation, mobile sweep, error states, staging smoke test with Abbas, go-live redirect from homora.fi

9

Database Schema & ERD

🗄️
Target: Supabase (PostgreSQL). 9 tables, 15 indexes, 4 triggers, 6 enums. Full SQL in 07-Database-Schema.md. Source of truth for all data decisions.

Entity Relationship Diagram

All Tables · All FKs
PK Primary key FK Foreign key UK Unique key T Trigger updates this field References
auth.users Supabase built-in
PK
id
uuid
email
text (managed by Supabase)
users
PK
id
uuid
→ auth.users (cascade)
role
user_role enum
customer | provider | admin
full_name
text NOT NULL
phone
text
avatar_url
text
T
updated_at
timestamptz
auto via trigger
provider_profiles
PK
id
uuid
FK
user_id
uuid UNIQUE
→ users.id (cascade)
Identity
UK
y_tunnus
text NOT NULL
Finnish biz ID NNNNNNN-N
stripe_account_id
text
Stripe Connect acct
Services
categories
service_category[] GIN
array, multi-category support
hourly_rate
numeric(8,2)
EUR, shown to customer
service_areas
text[] GIN
e.g. {helsinki,espoo}
Status & Quality
verification_status
verification_status enum
pending | approved | rejected
rejection_reason
text
admin_notes
text
internal; not shown to provider
T
rating_avg
numeric(3,2)
auto-recalculated after review
T
rating_count
int
auto-recalculated after review
response_time_hrs
numeric(4,1)
rolling average
cancellation_count
int
last 30 days
is_active
boolean
T
updated_at
timestamptz
provider_documents
PK
id
uuid
FK
provider_id
uuid
→ provider_profiles.id
document_type
text
id_front | id_back | biz_reg
storage_path
text
Supabase Storage path
uploaded_at
timestamptz
availability_slots
PK
id
uuid
FK
provider_id
uuid
→ provider_profiles.id
slot_date
date
start_time
time
e.g. 09:00
end_time
time
e.g. 11:00
is_available
boolean
false = blocked by provider
FK
booking_id
uuid nullable
→ bookings.id (set on reserve)
UNIQUE (provider_id, slot_date, start_time)
bookings Core transaction
PK
id
uuid
FK
customer_id
uuid
→ users.id
FK
provider_id
uuid
→ provider_profiles.id
FK
slot_id
uuid nullable
→ availability_slots.id
Scheduling
category
service_category enum
job_date
date
start_time
time
duration_hours
numeric(4,1)
Location
address_line1
text
address_city
text
address_zip
text
address_notes
text nullable
Financials (EUR)
hourly_rate_snapshot
numeric(8,2) immutable
labor_cost
numeric(10,2)
hours × rate_snapshot
materials_cost
numeric(10,2) default 0
platform_fee
numeric(10,2)
20% of labor_cost
total_charged
numeric(10,2)
labor + materials
provider_payout
numeric(10,2)
80% labor; set on completion
tax_deduction_estimate
numeric(10,2)
45% labor; pre-booking estimate
cancellation_fee
numeric(10,2)
50% if <24h; else 0
refund_amount
numeric(10,2)
total − cancellation_fee
Status & Stripe
status
booking_status enum
pending_provider → completed
response_deadline_at
timestamptz
created + 2h; auto-decline cron
cancellation_reason
cancellation_reason enum
UK
stripe_payment_intent_id
text
stripe_transfer_id
text
set after payout
payout_status
payout_status enum
pending | paid | failed
T
updated_at
timestamptz
reviews
PK
id
uuid
FK
booking_id
uuid UNIQUE
→ bookings.id; 1 review per booking
FK
reviewer_id
uuid
→ users.id (must be customer)
FK
provider_id
uuid
→ provider_profiles.id
rating
smallint CHECK 1–5
comment
text nullable
INSERT trigger → updates provider rating_avg
tax_receipts
PK
id
uuid
FK
booking_id
uuid UNIQUE
→ bookings.id; 1 receipt per booking
FK
customer_id
uuid
→ users.id
Kotitalousvähennys data
provider_y_tunnus
text
snapshot at time of job
provider_name
text
snapshot at time of job
service_date
date
address
text
labor_cost
numeric(10,2)
deductible_amount
numeric(10,2)
45% of labor_cost (locked)
total_paid
numeric(10,2)
pdf_storage_path
text
Supabase Storage path
notifications_log
PK
id
uuid
FK
user_id
uuid nullable
→ users.id
type
text
booking_confirmed | review_prompt…
channel
text
email | sms
recipient
text
email address or phone number
reference_id
uuid nullable
booking_id or provider_id
status
text
sent | failed

Relationship Matrix

FromCardinalityToVia / Notes
auth.users1 : 1usersid cascade delete — extends Supabase auth with profile data
users1 : 0..1provider_profilesuser_id UNIQUE — customer can become a provider; one profile only
users1 : manybookingscustomer_id — all bookings made by this user as a customer
users1 : manyreviewsreviewer_id — reviews written by this user
users1 : manytax_receiptscustomer_id — all receipts for this customer
users1 : manynotifications_loguser_id — audit of all emails/SMS sent
provider_profiles1 : manyprovider_documentsprovider_id — ID docs, business registration uploads
provider_profiles1 : manyavailability_slotsprovider_id — the provider's calendar
provider_profiles1 : manybookingsprovider_id — all jobs this provider fulfills
provider_profiles1 : manyreviewsprovider_id — all reviews received
availability_slots1 : 0..1bookingsbooking_id on slot — slot is locked when booking is created
bookings1 : 0..1reviewsbooking_id UNIQUE — one review per completed booking
bookings1 : 0..1tax_receiptsbooking_id UNIQUE — one receipt per completed booking

Enum Reference

EnumValuesUsed in
user_rolecustomer · provider · adminusers.role
verification_statuspending · approved · rejectedprovider_profiles.verification_status
booking_statuspending_providerconfirmedin_progresscompleted · cancelledbookings.status
service_categorycleaning · handyman · laundry · moving · carpet_cleaning · otherprovider_profiles.categories · bookings.category
cancellation_reasoncustomer_cancelled · provider_declined · admin_cancelled · no_showbookings.cancellation_reason
payout_statuspending · paid · failedbookings.payout_status

Trigger Map

TriggerTableEventEffect
users_updated_atusersBEFORE UPDATESets updated_at = now()
provider_profiles_updated_atprovider_profilesBEFORE UPDATESets updated_at = now()
bookings_updated_atbookingsBEFORE UPDATESets updated_at = now()
reviews_update_ratingreviewsAFTER INSERTRecalculates provider_profiles.rating_avg and rating_count

Index Reference

IndexTableTypePurpose
idx_provider_categoriesprovider_profilesGINFilter by service category array
idx_provider_service_areasprovider_profilesGINFilter by city/area array
idx_provider_verificationprovider_profilesBTreeAdmin review queue + public listing
idx_provider_y_tunnusprovider_profilesUniquePrevent duplicate Finnish biz IDs
idx_availability_provider_dateavailability_slotsBTreeProvider calendar view by date
idx_availability_availableavailability_slotsPartial BTreeOnly open slots shown to customers
idx_bookings_customerbookingsBTreeCustomer booking history (newest first)
idx_bookings_providerbookingsBTreeProvider job list by date
idx_bookings_statusbookingsBTreeStatus-based filtering and dashboards
idx_bookings_payoutbookingsPartial BTreePayout cron: only pending payouts
idx_bookings_response_deadlinebookingsPartial BTreeAuto-decline cron: overdue provider responses
idx_reviews_providerreviewsBTreeProvider review list (newest first)
idx_tax_receipts_customertax_receiptsBTreeCustomer receipt history (newest first)

Key Computed Values

hourly_rate_snapshot  = copied from provider_profiles.hourly_rate at booking creation (immutable)
labor_cost            = duration_hours × hourly_rate_snapshot
platform_fee          = labor_cost × 0.20   → Homora revenue per booking
total_charged         = labor_cost + materials_cost   → what customer pays via Stripe
provider_payout       = labor_cost × 0.80   → released to provider after job completion
tax_deduction_est     = labor_cost × 0.45   → shown pre-booking (estimate only)
deductible_amount     = labor_cost × 0.45   → locked onto tax_receipt after completion
cancellation_fee      = total_charged × 0.50  if cancelled ≤ 24h before start
                      = 0                      if cancelled > 24h before start
refund_amount         = total_charged − cancellation_fee
10

API Spec

Key Endpoints

MethodRouteDescription
GET/api/providersList providers filtered by category, service_area
GET/api/providers/:idProvider profile + availability
GET/api/providers/:id/availabilityAvailable slots for a given month
POST/api/bookingsCreate booking + Stripe PaymentIntent
POST/api/bookings/:id/confirmProvider confirms → slot locked → customer notified
POST/api/bookings/:id/completeMark complete → trigger Stripe capture + payout + PDF
POST/api/providers/onboardSubmit provider application
POST/api/admin/providers/:id/approveAdmin approves provider → profile goes live
GET/api/receipts/:id/pdfDownload tax receipt PDF (signed URL)
POST/api/reviewsSubmit review (post-completion only)

Error Codes

CodeMeaning
SLOT_UNAVAILABLEChosen time slot was taken between selection and submission
PAYMENT_FAILEDStripe PaymentIntent creation or capture failed
PROVIDER_NOT_APPROVEDProvider's verification_status is not 'approved'
RESPONSE_EXPIREDProvider's 2-hour response window has passed
REVIEW_ALREADY_SUBMITTEDA review already exists for this booking_id
11

User Stories Summary

P0 Stories — Required for Launch

IDStoryActor
US-001Browse verified cleaning providers in my areaCustomer
US-002See provider's full profile, bio, availability before bookingCustomer
US-010Book a provider for a specific date and time slotCustomer
US-011See the full price breakdown including kotitalousvähennys estimate before payingCustomer
US-012Pay by card at booking. Payment authorized, not captured until job complete.Customer
US-020Receive a kotitalousvähennys-compatible tax receipt after each completed jobCustomer
US-030Create an account with email and see all my bookings in one placeCustomer
US-100Apply to join Homora with Y-tunnus, photo, ID, and bank accountProvider
US-110Be notified of new bookings and accept/decline within 2 hoursProvider
US-111Mark a job as complete to trigger my payout and customer's receiptProvider
US-112Set my weekly availability so customers only book available slotsProvider
US-120See my earnings breakdown and know when I'll be paidProvider
US-200Review and approve provider applications (verify Y-tunnus, ID)Admin
US-201See all active bookings and handle disputesAdmin
US-202Trigger weekly payouts for all completed jobsAdmin
12

UX Flows

🔀
Full screen-by-screen flows with all states and edge cases are in 09-UX-Flows.md. Below is the critical path summary.

Critical Paths

FlowStepsKey edge cases
Customer books a jobHome → Browse → Profile → Book (4 steps) → Pay → ConfirmedSlot taken between steps; payment failure; provider no-respond
Provider gets a bookingNotification → Dashboard → Accept (full address revealed) → Job done → Mark complete2h window expiry auto-decline; customer no-show
Tax receipt deliveredJob completed → PDF generated → Emailed to customer → Available in My BookingsPDF generation failure → admin notified, manual within 24h
Provider onboardingApply (5 steps) → Admin review (24h) → Approved email → Set availability → LiveY-tunnus duplicate; Stripe Connect incomplete; rejection + resubmit
CancellationMy Bookings → Cancel → Policy check → Stripe refund → Slot released>24h = full refund; ≤24h = 50% charged
HOMORA Service Oy · Business & Strategy Documentation · v1.0 · February 2026 · Built by Mahdi Farimani