13 KiB
WaterTrek API (Frontend Handoff)
Base URL: /api/v1/
Auth: JWT Bearer token in Authorization: Bearer <access_token>
Auth + Accounts
Register Vendor
POST /api/v1/accounts/register/vendor/- Public endpoint
Request:
{
"email": "vendor@example.com",
"password": "StrongPassword123",
"first_name": "Nora",
"last_name": "Smith",
"phone_number": "+1-555-1234",
"business_name": "Nora Rentals",
"description": "Jet ski and boat rentals",
"contact_phone": "+1-555-1234",
"contact_email": "hello@norarentals.com",
"address_line1": "1 Harbor Way",
"city": "Miami",
"state": "FL",
"postal_code": "33101",
"country": "USA"
}
Response 201:
{
"id": 12,
"email": "vendor@example.com",
"first_name": "Nora",
"last_name": "Smith",
"phone_number": "+1-555-1234",
"is_vendor": true,
"is_customer": false,
"vendor_profile": {
"business_name": "Nora Rentals",
"slug": "nora-rentals",
"description": "Jet ski and boat rentals",
"contact_phone": "+1-555-1234",
"contact_email": "hello@norarentals.com",
"address_line1": "1 Harbor Way",
"address_line2": "",
"city": "Miami",
"state": "FL",
"postal_code": "33101",
"country": "USA",
"created_at": "2026-04-08T17:00:00Z",
"updated_at": "2026-04-08T17:00:00Z"
}
}
Login (JWT)
POST /api/v1/accounts/token/
Request:
{
"email": "vendor@example.com",
"password": "StrongPassword123"
}
Response 200:
{
"refresh": "<refresh_token>",
"access": "<access_token>"
}
Refresh JWT
POST /api/v1/accounts/token/refresh/
Request:
{
"refresh": "<refresh_token>"
}
Current User
GET /api/v1/accounts/me/- Requires JWT
Vendor Profile Setup / Update
GET /api/v1/accounts/vendor-profile/me/PATCH /api/v1/accounts/vendor-profile/me/- Requires vendor JWT
Patch example:
{
"business_name": "Nora Rentals LLC",
"description": "Premium water equipment rentals",
"city": "Fort Lauderdale"
}
Equipment APIs
Public Equipment List
GET /api/v1/equipment/items/- Public endpoint
- Query params:
category(category id or slug)location(icontains match)vendor_slugmin_pricemax_priceavailable_from(ISO datetime)available_to(ISO datetime)
Example:
GET /api/v1/equipment/items/?location=miami&min_price=100&max_price=300
Public Equipment Detail
GET /api/v1/equipment/items/{public_id}/- Public endpoint
- Marketing / click-through: Each successful detail response appends:
marketing_click_id(integer): pass this onPOST /api/v1/booking/bookings/request/asmarketing_click_idso bookings can be attributed to the visit (organic or campaign).click_traffic_type:"organic"or"marketing"(derived from query params below).
- Optional query params (standard UTM + common ad IDs) are read and stored on the click row for vendor reporting:
utm_source,utm_medium,utm_campaign,utm_term,utm_contentgclid,fbclid
- If any of those params is non-empty,
click_traffic_typeismarketing; otherwiseorganic.
Example:
GET /api/v1/equipment/items/jetski-001/?utm_source=google&utm_medium=cpc&utm_campaign=spring_sale
Vendor Inventory CRUD
GET /api/v1/equipment/vendor/items/POST /api/v1/equipment/vendor/items/GET /api/v1/equipment/vendor/items/{id}/PUT /api/v1/equipment/vendor/items/{id}/PATCH /api/v1/equipment/vendor/items/{id}/DELETE /api/v1/equipment/vendor/items/{id}/- Requires vendor JWT
Create payload:
{
"public_id": "jetski-001",
"title": "Yamaha Jet Ski",
"description": "Fast and stable for 2 riders",
"details": {
"engine_cc": 998,
"seats": 2
},
"location": "Miami Beach",
"price_per_day": "180.00",
"is_active": true,
"category_id": 1
}
Vendor Storefront by Slug
GET /api/v1/equipment/storefront/{slug}/- Public endpoint
- Returns vendor profile + active inventory list
Adventrues APIs
Public Adventure List
GET /api/v1/adventrues/offerings/- Public endpoint
- Query params:
category(category id or slug)location(matchesmeeting_point)vendor_slugmin_pricemax_priceavailable_from(ISO datetime)available_to(ISO datetime)
Public Adventure Detail
GET /api/v1/adventrues/offerings/{public_id}/- Public endpoint
- Same marketing / click-through behavior as equipment detail: response includes
marketing_click_idandclick_traffic_type; UTM /gclid/fbclidquery params are captured when present.
Vendor Adventure CRUD
GET /api/v1/adventrues/vendor/offerings/POST /api/v1/adventrues/vendor/offerings/GET /api/v1/adventrues/vendor/offerings/{id}/PUT /api/v1/adventrues/vendor/offerings/{id}/PATCH /api/v1/adventrues/vendor/offerings/{id}/DELETE /api/v1/adventrues/vendor/offerings/{id}/- Requires vendor JWT
Create payload:
{
"public_id": "sunset-kayak-001",
"title": "Sunset Kayak Tour",
"description": "Guided paddle with sunset views",
"meeting_point": "Biscayne Bay Marina",
"duration_minutes": 120,
"capacity": 10,
"price_per_person": "75.00",
"is_active": true,
"category_id": 1
}
Vendor Adventure Storefront by Slug
GET /api/v1/adventrues/storefront/{slug}/- Public endpoint
- Returns vendor profile + active adventure offerings
Booking APIs
Availability Check
GET /api/v1/booking/availability/- Public endpoint
- Required query params:
- exactly one of
equipment_item_idoradventure_offering_id starts_at(ISO datetime)ends_at(ISO datetime)
- exactly one of
- Rule notes:
- If availability slots exist for the target, booking requires at least one
is_available=trueslot fully covering the requested range. - Any overlapping
is_available=falseslot blocks the request. - If no slots exist yet, target is treated as bookable by default (subject to overlap checks).
- If availability slots exist for the target, booking requires at least one
Example:
GET /api/v1/booking/availability/?equipment_item_id=5&starts_at=2026-04-12T10:00:00Z&ends_at=2026-04-13T10:00:00Z
Response:
{
"equipment_item_id": 5,
"adventure_offering_id": null,
"starts_at": "2026-04-12T10:00:00Z",
"ends_at": "2026-04-13T10:00:00Z",
"is_available": true,
"conflicts": 0
}
Create Booking Request (Customer)
POST /api/v1/booking/bookings/request/- Requires customer JWT
Request:
{
"equipment_item_id": 5,
"starts_at": "2026-04-12T10:00:00Z",
"ends_at": "2026-04-14T10:00:00Z",
"customer_notes": "Need two life jackets",
"marketing_click_id": 9042
}
- Optional
marketing_click_id: integer from the latestGETdetail response (marketing_click_id) for the same equipment item or adventure offering. Must be within 90 days of the click. Omit for unattributed bookings (still allowed).
Response includes booking with status requested and listing_click (nullable FK id) when attribution was applied.
Adventure booking request example:
{
"adventure_offering_id": 9,
"starts_at": "2026-05-10T15:00:00Z",
"ends_at": "2026-05-10T17:00:00Z",
"participants_count": 3,
"customer_notes": "First-time kayakers"
}
List My Bookings
GET /api/v1/booking/bookings/- Requires JWT
- If caller is vendor: returns bookings for their vendor profile
- Else: returns bookings where caller is customer
Booking Detail
GET /api/v1/booking/bookings/{id}/- Requires JWT with ownership (customer or vendor on that booking)
Vendor Approve Booking
POST /api/v1/booking/bookings/{id}/approve/- Requires vendor JWT (own booking only)
Payload (optional):
{
"vendor_notes": "Approved, pickup after 9AM",
"note": "Ready for pickup window"
}
Vendor Decline Booking
POST /api/v1/booking/bookings/{id}/decline/- Requires vendor JWT (own booking only)
Customer Cancel Booking
POST /api/v1/booking/bookings/{id}/cancel/- Requires customer JWT (own booking only)
Payload (optional):
{
"note": "Trip postponed to next month"
}
Payment APIs (Mock Stripe)
Create Payment Intent (Mock)
POST /api/v1/payment/intents/- Requires customer JWT (must own booking)
- Booking must be in
approvedstate
Request:
{
"booking_id": 42,
"currency": "usd"
}
Response 201:
{
"payment": {
"id": 10,
"booking_id": 42,
"stripe_payment_intent_id": "pi_mock_123abc...",
"stripe_charge_id": "",
"amount": "360.00",
"currency": "usd",
"status": "requires_payment",
"created_at": "2026-04-08T18:00:00Z",
"updated_at": "2026-04-08T18:00:00Z"
},
"client_secret": "pi_secret_mock_123abc...",
"mocked": true
}
Payment Status
GET /api/v1/payment/{payment_id}/status/- Requires JWT (booking customer or booking vendor owner)
Stripe Webhook (Mocked)
POST /api/v1/payment/webhooks/stripe/- Public endpoint for local/mock integration
- Idempotent by
stripe_event_id
Request:
{
"stripe_event_id": "evt_mock_001",
"event_type": "payment_intent.succeeded",
"stripe_payment_intent_id": "pi_mock_123abc...",
"payload": {
"source": "frontend-test"
}
}
Supported event_type values:
payment_intent.processingpayment_intent.succeededpayment_intent.payment_failedcharge.refunded
Behavior:
- Updates
PaymentRecord.status - On
payment_intent.succeeded, booking auto-transitionsapproved -> confirmed - Stores webhook in
WebhookEventand enforces idempotency
Marketing APIs (UTM, click-through, vendor analytics)
Base path: /api/v1/marketing/
Track listing click (explicit)
POST /api/v1/marketing/track/click/- Public (optional JWT if the user is logged in; stored on the click when present)
- Use when the SPA opens a listing without calling the public detail endpoint (e.g. client-side route from cache). Otherwise prefer relying on
GETequipment/adventure detail, which records the click automatically.
Request:
{
"listing_type": "equipment",
"public_id": "jetski-001",
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "april_promo",
"utm_term": "",
"utm_content": "hero",
"gclid": "",
"fbclid": "",
"referrer": "https://mail.example.com/"
}
listing_type:"equipment"or"adventure"(must matchpublic_id).
Response 201:
{
"marketing_click_id": 9042,
"traffic_type": "marketing"
}
Vendor: list listing clicks
GET /api/v1/marketing/vendor/clicks/- Requires vendor JWT
- Required query params:
from,to— ISO-8601 datetimes (UTC), half-open range[from, to). - Optional filters:
traffic_type(organic|marketing),listing_type(equipment|adventure),utm_campaign(exact match).
Vendor: marketing summary (conversions)
GET /api/v1/marketing/vendor/summary/- Requires vendor JWT
- Required query params:
from,to— same as above.
Returns (shape):
clicks.organic/clicks.marketing/clicks.total— listing detail or track-click events in range.bookings_attributed.organic— bookings in range whoselisting_click.traffic_typeisorganic.bookings_attributed.marketing— bookings in range whoselisting_click.traffic_typeismarketing.bookings_attributed.unattributed— bookings in range with nolisting_click.conversion_rate_click_to_booking.organic—organicbookings ÷organicclicks (omitted /nullif clicks = 0).conversion_rate_click_to_booking.marketing— same for marketing.campaigns— up to 50 rows with non-emptyutm_campaignon clicks:utm_source,utm_medium,utm_campaign,clicks,bookings(bookings that referenced a click with that triple),conversion_rate.
Interpreting organic vs marketing: Traffic is classified per click from UTM / gclid / fbclid. To compare conversion rates fairly, the client should send marketing_click_id on checkout for both organic and paid visits when possible (so bookings are not left in unattributed).
Notes for React Integration
- All datetimes are ISO-8601 and UTC.
- Booking overlap protection is implemented for
requested,approved, andconfirmedwindows. requestedworks as a soft hold and blocks overlapping requests until resolved.total_priceis computed server-side:- equipment: rounded-up day count x
price_per_day - adventure:
participants_countxprice_per_person
- equipment: rounded-up day count x
- Payment APIs are currently mocked (Stripe-style IDs and webhook event flow) to unblock frontend integration before live Stripe keys are wired.
- Use
public_idfor equipment-facing detail pages and numericidfor vendor CRUD. - Booking domain services are implemented server-side for rules and transitions:
is_bookable(...)quote_booking(...)create_booking_request(...)transition_booking_status(...)
- Persist
marketing_click_idfrom listing detail (orPOST .../marketing/track/click/) and send it withPOST .../booking/bookings/request/so vendors can tie bookings to campaigns and compare organic vs marketing conversion inGET .../marketing/vendor/summary/.