1) Auth APIs (/api/auth/*)
1.1 Request OTP

Method: POST

Path: /api/auth/request-otp

Purpose: Generate/send OTP (demo OTP) for login or registration.

Body (JSON):

phone (string, required)

purpose (string, optional: "login" default, or "register")

Success Response (200):

success: true

message: "OTP sent (demo)"

otp: <demo otp>

Errors:

422 if phone missing

1.2 Register Vendor

Method: POST

Path: /api/auth/register

Purpose: Create a new vendor (initially inactive) and create vendor KYC record.

Body (JSON):

name (string, required)

phone (string, required)

email (string, required)

otp (string, required, must match DEMO_OTP)

Success Response (200):

success: true

message: "Registered. Proceed to KYC."

vendor_id (int)

vendor_code (string like VEN001)

Errors:

422 missing required fields

401 invalid OTP

409 vendor already exists (phone/email)

1.3 Login Vendor

Method: POST

Path: /api/auth/login

Purpose: Login with phone + OTP and issue vendor token.

Body (JSON):

phone (string, required)

otp (string, required, must match DEMO_OTP)

Success Response (200):

success: true

message: "Login ok"

token (string)

vendor (vendor row)

Errors:

422 missing phone/otp

401 invalid OTP

404 vendor not found

2) Vendor APIs (/api/vendor/*) (Auth required)

All vendor endpoints use require_vendor_auth().

2.1 Get Vendor Profile (Me)

Method: GET

Path: /api/vendor

Path (alias): /api/vendor/me

Purpose: Fetch vendor record + KYC record status/admin notes.

Success Response (200):

success: true

vendor: { ...vendor fields..., kyc_record_status, admin_notes }

2.2 Submit Vendor KYC

Method: POST

Path: /api/vendor/kyc

Purpose: Upload KYC documents and submit KYC.

Content-Type: multipart/form-data

Form Fields (required):

aadhar_front (file)

aadhar_back (file)

pan_front (file)

pan_back (file)

license_photo (file)

license_number (string)

Success Response (200):

success: true

message: "KYC submitted"

Errors:

422 if any required file/field missing

2.3 Update Vendor Profile

Method: PUT

Path: /api/vendor/profile

Purpose: Update vendor name/email.

Body (JSON):

name (string, optional)

email (string, optional)

Success Response (200):

success: true

message: "Profile updated"

2.4 Simulate Approval (Demo)

Method: POST

Path: /api/vendor/simulate-approval

Purpose: Set vendor approved + active (demo helper).

Success Response (200):

success: true

message: "Vendor activated (demo)"

3) Dashboard APIs (/api/dashboard/*) (Auth required)
3.1 Dashboard Summary

Method: GET

Path: /api/dashboard/summary

Purpose: Fetch dashboard KPI summary for vendor.

Includes in response:

today_earnings

wallet_balance

today_visits

tomorrow_visits

recent_completed_jobs (last 5)

total_earnings

total_jobs

Success Response (200):

success: true

data: { ... }

Note: There’s a small bug in your code:
$wallet = (int)$pdo->prepare(...)->execute(...) doesn’t fetch wallet; you later fetch correctly into $v. The $wallet variable is unused.

4) Leads APIs (/api/leads/*) (Auth required)
4.1 List Available Leads

Method: GET

Path: /api/leads

Purpose: List all currently available leads (is_available=1).

Success Response (200):

success: true

leads: [ ... ]

4.2 Get Lead By ID

Method: GET

Path: /api/leads/{leadId}

Purpose: Fetch a single lead.

Path Params:

leadId (int)

Success Response (200):

success: true

lead: { ... }

Errors:

404 lead not found

4.3 Pick a Lead (Create Job + Deduct Wallet)

Method: POST

Path: /api/leads/{leadId}/pick

Purpose: Vendor picks a lead (transactional):

checks lead availability (FOR UPDATE)

checks vendor wallet balance (FOR UPDATE)

deducts lead price from wallet

inserts wallet transaction (debit)

creates job (accepted)

marks lead unavailable

Path Params:

leadId (int)

Success Response (200):

success: true

message: "Lead picked"

job_id (int)

Errors:

409 lead not available

402 insufficient balance

5) Jobs APIs (/api/jobs/*) (Auth required)
5.1 Ongoing Jobs

Method: GET

Path: /api/jobs/ongoing

Purpose: Get jobs with status accepted or in_progress.

Success Response (200):

success: true

jobs: [ ...with lead fields... ]

5.2 Completed Jobs

Method: GET

Path: /api/jobs/completed

Purpose: Get completed jobs list.

Success Response (200):

success: true

jobs: [ ... ]

5.3 Start Job

Method: POST

Path: /api/jobs/{jobId}/start

Purpose: Start a job (accepted -> in_progress) and upload before photo.

Content-Type: multipart/form-data

Path Params:

jobId (int)

Form Fields (required):

before_photo (file)

Success Response (200):

success: true

message: "Job started"

Errors:

404 job not found

409 job cannot be started (wrong status)

422 before_photo required

5.4 Complete Job

Method: POST

Path: /api/jobs/{jobId}/complete

Purpose: Complete job (in_progress -> completed):

validate completion OTP

upload after photo

update job completion

credit vendor wallet + totals

add wallet transaction (earning)

Content-Type: multipart/form-data

Path Params:

jobId (int)

Form Fields (required):

otp (string, required, must match COMPLETION_OTP)

after_photo (file, required)

Success Response (200):

success: true

message: "Job completed"

earning (int)

Errors:

404 job not found

409 job cannot be completed (wrong status)

401 invalid completion OTP

422 after_photo required

6) Wallet APIs (/api/wallet/*) (Auth required)
6.1 Wallet Summary

Method: GET

Path: /api/wallet/summary

Purpose: Get vendor wallet_balance, total_earnings, total_jobs.

Success Response (200):

success: true

data: { wallet_balance, total_earnings, total_jobs }

6.2 Wallet History

Method: GET

Path: /api/wallet/history

Purpose: Get wallet transaction history (latest 200).

Success Response (200):

success: true

transactions: [ ... ]

6.3 Wallet Recharge (Demo)

Method: POST

Path: /api/wallet/recharge

Purpose: Add credit to wallet (demo).

Body (JSON):

amount (int, required, > 0)

Success Response (200):

success: true

message: "Recharged"

amount (int)

Errors:

422 amount must be > 0

7) Categories APIs (/api/categories/*) (Auth required in your code)

Uses require_vendor_auth() currently.

7.1 List Categories

Method: GET

Path: /api/categories

Query Params (optional):

only_active = 1 (default) or 0

Success Response (200):

success: true

categories: [ { id, name, thumbnail, is_active, created_at } ]

7.2 Get Category By ID

Method: GET

Path: /api/categories/{id}

Success Response (200):

success: true

category: { id, name, thumbnail, is_active, created_at }

Errors:

404 category not found

7.3 Create Category

Method: POST

Path: /api/categories

Body (JSON):

name (string, required)

thumbnail (string, optional)

is_active (boolean/int, optional default 1)

Success Response (200):

success: true

message: "Category created"

id (int)

Errors:

422 name required

409 category already exists

7.4 Update Category

Method: PUT

Path: /api/categories/{id}

Body (JSON): (any of)

name (string)

thumbnail (string)

is_active (boolean/int)

Success Response (200):

success: true

message: "Category updated"

Errors:

404 category not found

422 invalid name

409 category name already used

7.5 Delete Category

Method: DELETE

Path: /api/categories/{id}

Purpose: Deletes only if no subcategories and no services exist for category.

Success Response (200):

success: true

message: "Category deleted"

Errors:

409 category has subcategories

409 category has services

8) Subcategories APIs (/api/subcategories/*) (Auth required)
8.1 List Subcategories

Method: GET

Path: /api/subcategories

Query Params (optional):

category_id (int, optional; filter)

only_active = 1 default or 0

Success Response (200):

success: true

subcategories: [ { id, category_id, name, thumbnail, is_active, created_at, category_name } ]

8.2 Get Subcategory By ID

Method: GET

Path: /api/subcategories/{id}

Success Response (200):

success: true

subcategory: { ...subcategory fields..., category_name }

Errors:

404 subcategory not found

8.3 Create Subcategory

Method: POST

Path: /api/subcategories

Body (JSON):

category_id (int, required)

name (string, required)

thumbnail (string, optional)

is_active (boolean/int, optional default 1)

Success Response (200):

success: true

message: "Subcategory created"

id (int)

Errors:

422 category_id and name required

404 category not found

409 subcategory already exists in this category

8.4 Update Subcategory

Method: PUT

Path: /api/subcategories/{id}

Body (JSON): (any of)

category_id (int)

name (string)

thumbnail (string)

is_active (boolean/int)

Success Response (200):

success: true

message: "Subcategory updated"

Errors:

404 subcategory not found

422 invalid data / missing required

409 subcategory name already used in this category

8.5 Delete Subcategory

Method: DELETE

Path: /api/subcategories/{id}

Purpose: Block delete if services exist for this subcategory.

Success Response (200):

success: true

message: "Subcategory deleted"

Errors:

409 subcategory has services

9) Services APIs (/api/services/*) (Auth required)
9.1 List Services

Method: GET

Path: /api/services

Query Params (optional):

category_id (int, optional)

subcategory_id (int, optional)

only_active = 1 default or 0

Success Response (200):

success: true

services: [ ... ]

Notes:

Each service includes category_name, subcategory_name

images_json is decoded and returned as images array

9.2 Get Service By ID

Method: GET

Path: /api/services/{id}

Success Response (200):

success: true

service: { ...service fields..., category_name, subcategory_name, images: [] }

Errors:

404 service not found

9.3 Create Service

Method: POST

Path: /api/services

Body (JSON):

category_id (int, required)

subcategory_id (int, required)

title (string, required)

price (int, required > 0)

thumbnail (string, required)

description (string, optional)

images (array of strings, optional)

is_active (boolean/int, optional default 1)

Success Response (200):

success: true

message: "Service created"

id (int)

Errors:

422 missing required fields or invalid category/subcategory pair

422 invalid category/subcategory pair (subcategory not under category)

9.4 Update Service

Method: PUT

Path: /api/services/{id}

Body (JSON):

category_id (int, optional)

subcategory_id (int, optional)

title (string, optional)

price (int, optional)

description (string, optional)

thumbnail (string, optional)

images (array, optional; if provided replaces existing images_json)

is_active (boolean/int, optional)

Success Response (200):

success: true

message: "Service updated"

Errors:

404 service not found

422 missing required fields after merge or invalid category/subcategory pair

9.5 Delete Service

Method: DELETE

Path: /api/services/{id}

Success Response (200):

success: true

message: "Service deleted"

10) Global / Fallback
10.1 Not Found

Any Method / Any Path (unmatched)

Responds:

404

{ success:false, message:"Not found" }

10.2 Server Error

If exception occurs:

500

{ success:false, message:"Server error", error:"..." }

Quick Summary List (for copy/paste into README)

POST /api/auth/request-otp

POST /api/auth/register

POST /api/auth/login

GET /api/vendor

GET /api/vendor/me

POST /api/vendor/kyc

PUT /api/vendor/profile

POST /api/vendor/simulate-approval

GET /api/dashboard/summary

GET /api/leads

GET /api/leads/{leadId}

POST /api/leads/{leadId}/pick

GET /api/jobs/ongoing

GET /api/jobs/completed

POST /api/jobs/{jobId}/start

POST /api/jobs/{jobId}/complete

GET /api/wallet/summary

GET /api/wallet/history

POST /api/wallet/recharge

GET /api/categories

GET /api/categories/{id}

POST /api/categories

PUT /api/categories/{id}

DELETE /api/categories/{id}

GET /api/subcategories

GET /api/subcategories/{id}

POST /api/subcategories

PUT /api/subcategories/{id}

DELETE /api/subcategories/{id}

GET /api/services

GET /api/services/{id}

POST /api/services

PUT /api/services/{id}

DELETE /api/services/{id}