Files
thrillwiki_django_no_react/docs/frontend.md

110 KiB

ThrillWiki Frontend API Documentation

Last Updated: January 28, 2025

This document provides comprehensive documentation for frontend developers on how to use the ThrillWiki API endpoints and features, including ALL possible responses and endpoints available in the system.

Base URL

All API endpoints are prefixed with /api/v1/ unless otherwise specified.

Authentication

Most endpoints require authentication via Bearer token in the Authorization header:

Authorization: Bearer your_token_here

Content Types

All POST/PUT/PATCH requests should use Content-Type: application/json unless uploading files (use multipart/form-data).

Rate Limiting

  • Authenticated users: 1000 requests per hour
  • Anonymous users: 100 requests per hour
  • Rate limit headers are included in all responses:
    • X-RateLimit-Limit: Maximum requests allowed
    • X-RateLimit-Remaining: Requests remaining in current window
    • X-RateLimit-Reset: Unix timestamp when limit resets

Table of Contents

  1. Authentication API
  2. Parks API
  3. Comprehensive Rides Filtering API
  4. Ride Models API
  5. Roller Coaster Statistics API
  6. Maps API
  7. User Accounts API
  8. Rankings API
  9. Trending & New Content API
  10. Stats API
  11. Photo Management
  12. Search and Autocomplete
  13. Core Entity Search API
  14. Health Check API
  15. Email API
  16. History API
  17. Companies API
  18. Park Areas API
  19. Reviews API
  20. Moderation API
  21. Error Handling
  22. Performance Considerations

Authentication API

Base Endpoints

POST /api/v1/auth/login/
POST /api/v1/auth/signup/
POST /api/v1/auth/logout/
GET /api/v1/auth/user/
POST /api/v1/auth/password/reset/
POST /api/v1/auth/password/change/
GET /api/v1/auth/providers/
GET /api/v1/auth/status/

Login

// Login request
const response = await fetch('/api/v1/auth/login/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    username: 'user@example.com',
    password: 'password123'
  })
});

// Success response (200)
{
  "user": {
    "id": 1,
    "username": "user@example.com",
    "email": "user@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "is_staff": false,
    "is_superuser": false,
    "date_joined": "2024-01-01T00:00:00Z"
  },
  "token": "auth_token_here",
  "message": "Login successful"
}

// Error response (400/401)
{
  "detail": "Invalid credentials",
  "errors": {
    "non_field_errors": ["Unable to log in with provided credentials."]
  }
}

Signup

// Signup request
const response = await fetch('/api/v1/auth/signup/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    username: 'newuser@example.com',
    email: 'newuser@example.com',
    password: 'securepassword123',
    first_name: 'Jane',
    last_name: 'Smith'
  })
});

// Success response (201)
{
  "user": {
    "id": 2,
    "username": "newuser@example.com",
    "email": "newuser@example.com",
    "first_name": "Jane",
    "last_name": "Smith",
    "is_staff": false,
    "is_superuser": false,
    "date_joined": "2024-01-28T15:30:00Z"
  },
  "token": "new_auth_token_here",
  "message": "Account created successfully"
}

Current User

// Get current user info
const response = await fetch('/api/v1/auth/user/', {
  headers: {
    'Authorization': 'Bearer your_token_here'
  }
});

// Response (200)
{
  "id": 1,
  "username": "user@example.com",
  "email": "user@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "is_staff": false,
  "is_superuser": false,
  "date_joined": "2024-01-01T00:00:00Z",
  "profile": {
    "bio": "Theme park enthusiast",
    "location": "Ohio, USA",
    "website": "https://example.com",
    "avatar": "https://imagedelivery.net/account-hash/avatar123/public"
  }
}

Social Providers

// Get available social login providers
const response = await fetch('/api/v1/auth/providers/');

// Response (200)
{
  "providers": [
    {
      "id": "google",
      "name": "Google",
      "login_url": "/auth/google/login/",
      "enabled": true
    },
    {
      "id": "facebook",
      "name": "Facebook", 
      "login_url": "/auth/facebook/login/",
      "enabled": true
    }
  ]
}

Auth Status

// Check authentication status
const response = await fetch('/api/v1/auth/status/');

// Authenticated response (200)
{
  "authenticated": true,
  "user": {
    "id": 1,
    "username": "user@example.com",
    "is_staff": false
  }
}

// Unauthenticated response (200)
{
  "authenticated": false,
  "user": null
}

Parks API

Base Endpoints

GET /api/v1/parks/
POST /api/v1/parks/
GET /api/v1/parks/{id}/
PUT /api/v1/parks/{id}/
PATCH /api/v1/parks/{id}/
DELETE /api/v1/parks/{id}/
GET /api/v1/parks/filter-options/
GET /api/v1/parks/search/companies/
GET /api/v1/parks/search-suggestions/
PATCH /api/v1/parks/{id}/image-settings/
GET /api/v1/parks/{park_pk}/photos/
POST /api/v1/parks/{park_pk}/photos/
GET /api/v1/parks/{park_pk}/photos/{id}/
PUT /api/v1/parks/{park_pk}/photos/{id}/
PATCH /api/v1/parks/{park_pk}/photos/{id}/
DELETE /api/v1/parks/{park_pk}/photos/{id}/

List Parks

// Get parks with filtering
const response = await fetch('/api/v1/parks/?search=cedar&status=OPERATING&min_rating=4.0&ordering=-average_rating');

// Response (200)
{
  "count": 25,
  "next": "http://localhost:8000/api/v1/parks/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "Cedar Point",
      "slug": "cedar-point",
      "status": "OPERATING",
      "description": "America's Roller Coast",
      "average_rating": 4.5,
      "coaster_count": 17,
      "ride_count": 70,
      "location": {
        "city": "Sandusky",
        "state": "Ohio",
        "country": "United States",
        "latitude": 41.4793,
        "longitude": -82.6833
      },
      "operator": {
        "id": 1,
        "name": "Cedar Fair",
        "slug": "cedar-fair"
      },
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    }
  ]
}

Park Detail

// Get detailed park information
const response = await fetch('/api/v1/parks/1/');

// Response (200)
{
  "id": 1,
  "name": "Cedar Point",
  "slug": "cedar-point",
  "status": "OPERATING",
  "description": "America's Roller Coast featuring world-class roller coasters",
  "opening_date": "1870-01-01",
  "closing_date": null,
  "operating_season": "May - October",
  "size_acres": 364.0,
  "website": "https://cedarpoint.com",
  "average_rating": 4.5,
  "coaster_count": 17,
  "ride_count": 70,
  "location": {
    "latitude": 41.4793,
    "longitude": -82.6833,
    "address": "1 Cedar Point Dr",
    "city": "Sandusky",
    "state": "Ohio",
    "country": "United States",
    "postal_code": "44870",
    "formatted_address": "1 Cedar Point Dr, Sandusky, OH 44870, United States"
  },
  "operator": {
    "id": 1,
    "name": "Cedar Fair",
    "slug": "cedar-fair"
  },
  "property_owner": {
    "id": 1,
    "name": "Cedar Fair",
    "slug": "cedar-fair"
  },
  "areas": [
    {
      "id": 1,
      "name": "Frontier Town",
      "slug": "frontier-town",
      "description": "Wild West themed area"
    },
    {
      "id": 2,
      "name": "Millennium Island",
      "slug": "millennium-island",
      "description": "Home to Millennium Force"
    }
  ],
  "photos": [
    {
      "id": 456,
      "image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
      "image_variants": {
        "thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
        "medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
        "large": "https://imagedelivery.net/account-hash/def789ghi012/large",
        "public": "https://imagedelivery.net/account-hash/def789ghi012/public"
      },
      "caption": "Beautiful park entrance",
      "alt_text": "Cedar Point main entrance with flags",
      "is_primary": true
    }
  ],
  "primary_photo": {
    "id": 456,
    "image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
      "large": "https://imagedelivery.net/account-hash/def789ghi012/large",
      "public": "https://imagedelivery.net/account-hash/def789ghi012/public"
    },
    "caption": "Beautiful park entrance",
    "alt_text": "Cedar Point main entrance with flags"
  },
  "banner_image": {
    "id": 456,
    "image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
      "large": "https://imagedelivery.net/account-hash/def789ghi012/large",
      "public": "https://imagedelivery.net/account-hash/def789ghi012/public"
    },
    "caption": "Beautiful park entrance",
    "alt_text": "Cedar Point main entrance with flags"
  },
  "card_image": {
    "id": 456,
    "image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
      "large": "https://imagedelivery.net/account-hash/def789ghi012/large",
      "public": "https://imagedelivery.net/account-hash/def789ghi012/public"
    },
    "caption": "Beautiful park entrance",
    "alt_text": "Cedar Point main entrance with flags"
  },
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Create Park

// Create a new park
const response = await fetch('/api/v1/parks/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    name: "New Theme Park",
    description: "An exciting new theme park",
    status: "OPERATING",
    opening_date: "2024-05-01",
    size_acres: 150.0,
    website: "https://newthemepark.com",
    operator_id: 1
  })
});

// Success response (201)
{
  "id": 25,
  "name": "New Theme Park",
  "slug": "new-theme-park",
  "status": "OPERATING",
  "description": "An exciting new theme park",
  "opening_date": "2024-05-01",
  "size_acres": 150.0,
  "website": "https://newthemepark.com",
  "operator": {
    "id": 1,
    "name": "Theme Park Operators Inc",
    "slug": "theme-park-operators-inc"
  },
  "created_at": "2024-01-28T15:30:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Park Filter Options

// Get available filter options for parks
const response = await fetch('/api/v1/parks/filter-options/');

// Response (200)
{
  "statuses": [
    ["OPERATING", "Operating"],
    ["CLOSED_TEMP", "Temporarily Closed"],
    ["CLOSED_PERM", "Permanently Closed"],
    ["UNDER_CONSTRUCTION", "Under Construction"],
    ["PLANNED", "Planned"]
  ],
  "countries": [
    "United States",
    "Canada",
    "United Kingdom",
    "Germany",
    "Japan"
  ],
  "operators": [
    {
      "id": 1,
      "name": "Cedar Fair",
      "slug": "cedar-fair",
      "park_count": 12
    },
    {
      "id": 2,
      "name": "Six Flags",
      "slug": "six-flags",
      "park_count": 27
    }
  ],
  "ordering_options": [
    {"value": "name", "label": "Name (A-Z)"},
    {"value": "-name", "label": "Name (Z-A)"},
    {"value": "opening_date", "label": "Opening Date (Oldest First)"},
    {"value": "-opening_date", "label": "Opening Date (Newest First)"},
    {"value": "average_rating", "label": "Rating (Lowest First)"},
    {"value": "-average_rating", "label": "Rating (Highest First)"},
    {"value": "coaster_count", "label": "Coaster Count (Fewest First)"},
    {"value": "-coaster_count", "label": "Coaster Count (Most First)"}
  ],
  "filter_ranges": {
    "rating": {"min": 1, "max": 10, "step": 0.1},
    "size_acres": {"min": 0, "max": 1000, "step": 10, "unit": "acres"}
  }
}

Comprehensive Rides Filtering API

Base Endpoints

GET /api/v1/rides/
POST /api/v1/rides/
GET /api/v1/rides/{id}/
PUT /api/v1/rides/{id}/
PATCH /api/v1/rides/{id}/
DELETE /api/v1/rides/{id}/
GET /api/v1/rides/filter-options/
GET /api/v1/rides/search/companies/
GET /api/v1/rides/search/ride-models/
GET /api/v1/rides/search-suggestions/
PATCH /api/v1/rides/{id}/image-settings/
GET /api/v1/rides/{ride_pk}/photos/
POST /api/v1/rides/{ride_pk}/photos/

List Rides with Comprehensive Filtering

The rides API supports 25+ comprehensive filter parameters for complex queries:

// Complex ride filtering example
const params = new URLSearchParams({
  search: 'steel vengeance',
  category: 'RC',
  status: 'OPERATING',
  park_slug: 'cedar-point',
  manufacturer_slug: 'rocky-mountain-construction',
  min_rating: '8.0',
  min_height_ft: '200',
  min_speed_mph: '70',
  has_inversions: 'true',
  track_material: 'HYBRID',
  roller_coaster_type: 'SITDOWN',
  launch_type: 'CHAIN',
  min_opening_year: '2015',
  max_opening_year: '2023',
  ordering: '-average_rating'
});

const response = await fetch(`/api/v1/rides/?${params.toString()}`);

// Response (200)
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "category": "RC",
      "status": "OPERATING",
      "description": "Hybrid roller coaster featuring RMC I-Box track",
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "average_rating": 4.8,
      "capacity_per_hour": 1200,
      "opening_date": "2018-05-05",
      "closing_date": null,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    }
  ]
}

Available Filter Parameters

const filterParams = {
  // Text search
  search: 'steel vengeance',
  
  // Category filters (multiple allowed)
  category: ['RC', 'DR'], // RC, DR, FR, WR, TR, OT
  
  // Status filters (multiple allowed)
  status: ['OPERATING', 'CLOSED_TEMP'], // OPERATING, CLOSED_TEMP, SBNO, CLOSING, CLOSED_PERM, UNDER_CONSTRUCTION, DEMOLISHED, RELOCATED
  
  // Park filters
  park_id: 1,
  park_slug: 'cedar-point',
  
  // Company filters
  manufacturer_id: 1,
  manufacturer_slug: 'bolliger-mabillard',
  designer_id: 2,
  designer_slug: 'rocky-mountain-construction',
  
  // Ride model filters
  ride_model_id: 5,
  ride_model_slug: 'dive-coaster', // requires manufacturer_slug
  
  // Rating filters
  min_rating: 8.5,
  max_rating: 9.0,
  
  // Height requirement filters
  min_height_requirement: 48, // inches
  max_height_requirement: 54,
  
  // Capacity filters
  min_capacity: 1000, // riders per hour
  max_capacity: 2000,
  
  // Date filters
  opening_year: 2020,
  min_opening_year: 2015,
  max_opening_year: 2023,
  
  // Roller coaster specific filters
  roller_coaster_type: 'INVERTED', // SITDOWN, INVERTED, FLYING, STANDUP, WING, DIVE, FAMILY, WILD_MOUSE, SPINNING, FOURTH_DIMENSION, OTHER
  track_material: 'STEEL', // STEEL, WOOD, HYBRID
  launch_type: 'LSM', // CHAIN, LSM, HYDRAULIC, GRAVITY, OTHER
  
  // Physical specification filters
  min_height_ft: 200,
  max_height_ft: 400,
  min_speed_mph: 60,
  max_speed_mph: 120,
  min_inversions: 3,
  max_inversions: 8,
  has_inversions: true, // boolean filter
  
  // Pagination
  page: 2,
  page_size: 50, // max 1000
  
  // Ordering
  ordering: '-average_rating' // name, -name, opening_date, -opening_date, average_rating, -average_rating, capacity_per_hour, -capacity_per_hour, created_at, -created_at, height_ft, -height_ft, speed_mph, -speed_mph
};

Ride Detail

// Get detailed ride information
const response = await fetch('/api/v1/rides/1/');

// Response (200)
{
  "id": 1,
  "name": "Steel Vengeance",
  "slug": "steel-vengeance",
  "category": "RC",
  "status": "OPERATING",
  "post_closing_status": null,
  "description": "Hybrid roller coaster featuring RMC I-Box track",
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "park_area": {
    "id": 1,
    "name": "Frontier Town",
    "slug": "frontier-town"
  },
  "opening_date": "2018-05-05",
  "closing_date": null,
  "status_since": "2018-05-05",
  "min_height_in": 48,
  "max_height_in": null,
  "capacity_per_hour": 1200,
  "ride_duration_seconds": 150,
  "average_rating": 4.8,
  "manufacturer": {
    "id": 1,
    "name": "Rocky Mountain Construction",
    "slug": "rocky-mountain-construction"
  },
  "designer": {
    "id": 1,
    "name": "Rocky Mountain Construction",
    "slug": "rocky-mountain-construction"
  },
  "ride_model": {
    "id": 5,
    "name": "I-Box Track Hybrid Coaster",
    "description": "Steel track on wooden structure",
    "category": "RC",
    "manufacturer": {
      "id": 1,
      "name": "Rocky Mountain Construction",
      "slug": "rocky-mountain-construction"
    }
  },
  "photos": [
    {
      "id": 123,
      "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
      "image_variants": {
        "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
        "medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
        "large": "https://imagedelivery.net/account-hash/abc123def456/large",
        "public": "https://imagedelivery.net/account-hash/abc123def456/public"
      },
      "caption": "Amazing roller coaster photo",
      "alt_text": "Steel Vengeance racing through the structure",
      "is_primary": true,
      "photo_type": "exterior"
    }
  ],
  "primary_photo": {
    "id": 123,
    "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
      "large": "https://imagedelivery.net/account-hash/abc123def456/large",
      "public": "https://imagedelivery.net/account-hash/abc123def456/public"
    },
    "caption": "Amazing roller coaster photo",
    "alt_text": "Steel Vengeance racing through the structure",
    "photo_type": "exterior"
  },
  "banner_image": {
    "id": 123,
    "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
      "large": "https://imagedelivery.net/account-hash/abc123def456/large",
      "public": "https://imagedelivery.net/account-hash/abc123def456/public"
    },
    "caption": "Amazing roller coaster photo",
    "alt_text": "Steel Vengeance racing through the structure",
    "photo_type": "exterior"
  },
  "card_image": {
    "id": 123,
    "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
      "large": "https://imagedelivery.net/account-hash/abc123def456/large",
      "public": "https://imagedelivery.net/account-hash/abc123def456/public"
    },
    "caption": "Amazing roller coaster photo",
    "alt_text": "Steel Vengeance racing through the structure",
    "photo_type": "exterior",
    "is_fallback": false
  },
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Filter Options Endpoint

// Get comprehensive filter metadata
const response = await fetch('/api/v1/rides/filter-options/');

// Response (200)
{
  "categories": [
    ["RC", "Roller Coaster"],
    ["DR", "Dark Ride"],
    ["FR", "Flat Ride"],
    ["WR", "Water Ride"],
    ["TR", "Transport"],
    ["OT", "Other"]
  ],
  "statuses": [
    ["OPERATING", "Operating"],
    ["CLOSED_TEMP", "Temporarily Closed"],
    ["SBNO", "Standing But Not Operating"],
    ["CLOSING", "Closing Soon"],
    ["CLOSED_PERM", "Permanently Closed"],
    ["UNDER_CONSTRUCTION", "Under Construction"],
    ["DEMOLISHED", "Demolished"],
    ["RELOCATED", "Relocated"]
  ],
  "roller_coaster_types": [
    ["SITDOWN", "Sit Down"],
    ["INVERTED", "Inverted"],
    ["FLYING", "Flying"],
    ["STANDUP", "Stand Up"],
    ["WING", "Wing"],
    ["DIVE", "Dive"],
    ["FAMILY", "Family"],
    ["WILD_MOUSE", "Wild Mouse"],
    ["SPINNING", "Spinning"],
    ["FOURTH_DIMENSION", "4th Dimension"],
    ["OTHER", "Other"]
  ],
  "track_materials": [
    ["STEEL", "Steel"],
    ["WOOD", "Wood"],
    ["HYBRID", "Hybrid"]
  ],
  "launch_types": [
    ["CHAIN", "Chain Lift"],
    ["LSM", "LSM Launch"],
    ["HYDRAULIC", "Hydraulic Launch"],
    ["GRAVITY", "Gravity"],
    ["OTHER", "Other"]
  ],
  "ordering_options": [
    {"value": "name", "label": "Name (A-Z)"},
    {"value": "-name", "label": "Name (Z-A)"},
    {"value": "opening_date", "label": "Opening Date (Oldest First)"},
    {"value": "-opening_date", "label": "Opening Date (Newest First)"},
    {"value": "average_rating", "label": "Rating (Lowest First)"},
    {"value": "-average_rating", "label": "Rating (Highest First)"},
    {"value": "capacity_per_hour", "label": "Capacity (Lowest First)"},
    {"value": "-capacity_per_hour", "label": "Capacity (Highest First)"},
    {"value": "created_at", "label": "Date Added (Oldest First)"},
    {"value": "-created_at", "label": "Date Added (Newest First)"},
    {"value": "height_ft", "label": "Height (Shortest First)"},
    {"value": "-height_ft", "label": "Height (Tallest First)"},
    {"value": "speed_mph", "label": "Speed (Slowest First)"},
    {"value": "-speed_mph", "label": "Speed (Fastest First)"}
  ],
  "filter_ranges": {
    "rating": {"min": 1, "max": 10, "step": 0.1},
    "height_requirement": {"min": 30, "max": 90, "step": 1, "unit": "inches"},
    "capacity": {"min": 0, "max": 5000, "step": 50, "unit": "riders/hour"},
    "height_ft": {"min": 0, "max": 500, "step": 5, "unit": "feet"},
    "speed_mph": {"min": 0, "max": 150, "step": 5, "unit": "mph"},
    "inversions": {"min": 0, "max": 20, "step": 1, "unit": "inversions"},
    "opening_year": {"min": 1800, "max": 2030, "step": 1, "unit": "year"}
  },
  "boolean_filters": [
    {
      "key": "has_inversions",
      "label": "Has Inversions",
      "description": "Filter roller coasters with or without inversions"
    }
  ]
}

Ride Models API

Base Endpoints

GET /api/v1/rides/manufacturers/{manufacturer_slug}/
POST /api/v1/rides/manufacturers/{manufacturer_slug}/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/
PUT /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/
PATCH /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/
DELETE /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/
GET /api/v1/rides/manufacturers/search/
GET /api/v1/rides/manufacturers/filter-options/
GET /api/v1/rides/manufacturers/stats/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/variants/
POST /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/variants/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/variants/{id}/
PUT /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/variants/{id}/
PATCH /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/variants/{id}/
DELETE /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/variants/{id}/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/technical-specs/
POST /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/technical-specs/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/technical-specs/{id}/
PUT /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/technical-specs/{id}/
PATCH /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/technical-specs/{id}/
DELETE /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/technical-specs/{id}/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/photos/
POST /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/photos/
GET /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/photos/{id}/
PUT /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/photos/{id}/
PATCH /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/photos/{id}/
DELETE /api/v1/rides/manufacturers/{manufacturer_slug}/{ride_model_slug}/photos/{id}/

List Ride Models for Manufacturer

// Get all ride models for a manufacturer
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/');

// Response (200)
{
  "count": 15,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "Dive Coaster",
      "slug": "dive-coaster",
      "description": "Vertical drop roller coaster with wide trains",
      "category": "RC",
      "manufacturer": {
        "id": 1,
        "name": "Bolliger & Mabillard",
        "slug": "bolliger-mabillard"
      },
      "ride_count": 12,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    },
    {
      "id": 2,
      "name": "Inverted Coaster",
      "slug": "inverted-coaster",
      "description": "Suspended roller coaster with inversions",
      "category": "RC",
      "manufacturer": {
        "id": 1,
        "name": "Bolliger & Mabillard",
        "slug": "bolliger-mabillard"
      },
      "ride_count": 45,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    }
  ]
}

Get Specific Ride Model

// Get detailed ride model information
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/');

// Response (200)
{
  "id": 1,
  "name": "Dive Coaster",
  "slug": "dive-coaster",
  "description": "Vertical drop roller coaster with wide trains featuring a 90-degree drop",
  "category": "RC",
  "manufacturer": {
    "id": 1,
    "name": "Bolliger & Mabillard",
    "slug": "bolliger-mabillard"
  },
  "ride_count": 12,
  "rides": [
    {
      "id": 5,
      "name": "Valravn",
      "slug": "valravn",
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "opening_date": "2016-05-07"
    },
    {
      "id": 8,
      "name": "Yukon Striker",
      "slug": "yukon-striker",
      "park": {
        "id": 3,
        "name": "Canada's Wonderland",
        "slug": "canadas-wonderland"
      },
      "opening_date": "2019-05-03"
    }
  ],
  "specifications": {
    "typical_height_range": "200-300 ft",
    "typical_speed_range": "70-95 mph",
    "typical_inversions": "0-3",
    "train_configuration": "Wide trains with 8-10 riders per row"
  },
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Create Ride Model

// Create a new ride model for a manufacturer
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    name: "New Coaster Model",
    description: "Revolutionary new coaster design",
    category: "RC"
  })
});

// Success response (201)
{
  "id": 25,
  "name": "New Coaster Model",
  "slug": "new-coaster-model",
  "description": "Revolutionary new coaster design",
  "category": "RC",
  "manufacturer": {
    "id": 1,
    "name": "Bolliger & Mabillard",
    "slug": "bolliger-mabillard"
  },
  "ride_count": 0,
  "created_at": "2024-01-28T15:30:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Update Ride Model

// Update a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    description: "Updated description with more details about the vertical drop experience"
  })
});

// Success response (200)
{
  "id": 1,
  "name": "Dive Coaster",
  "slug": "dive-coaster",
  "description": "Updated description with more details about the vertical drop experience",
  "category": "RC",
  "manufacturer": {
    "id": 1,
    "name": "Bolliger & Mabillard",
    "slug": "bolliger-mabillard"
  },
  "ride_count": 12,
  "updated_at": "2024-01-28T15:30:00Z"
}

Delete Ride Model

// Delete a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer your_token_here'
  }
});

// Success response (204)
// No content returned

// Error response if ride model has associated rides (409)
{
  "detail": "Cannot delete ride model with associated rides. Please reassign or delete associated rides first.",
  "associated_rides_count": 12
}
// Search ride models globally (not manufacturer-specific)
const response = await fetch('/api/v1/rides/manufacturers/search/?q=dive&category=RC');

// Response (200)
{
  "count": 5,
  "results": [
    {
      "id": 1,
      "name": "Dive Coaster",
      "slug": "dive-coaster",
      "manufacturer": {
        "id": 1,
        "name": "Bolliger & Mabillard",
        "slug": "bolliger-mabillard"
      },
      "ride_count": 12,
      "category": "RC"
    },
    {
      "id": 15,
      "name": "Dive Machine",
      "slug": "dive-machine",
      "manufacturer": {
        "id": 5,
        "name": "Gerstlauer",
        "slug": "gerstlauer"
      },
      "ride_count": 3,
      "category": "RC"
    }
  ]
}

Ride Model Filter Options

// Get filter options for ride models
const response = await fetch('/api/v1/rides/manufacturers/filter-options/');

// Response (200)
{
  "categories": [
    ["RC", "Roller Coaster"],
    ["DR", "Dark Ride"],
    ["FR", "Flat Ride"],
    ["WR", "Water Ride"],
    ["TR", "Transport"],
    ["OT", "Other"]
  ],
  "manufacturers": [
    {
      "id": 1,
      "name": "Bolliger & Mabillard",
      "slug": "bolliger-mabillard",
      "model_count": 15
    },
    {
      "id": 2,
      "name": "Intamin",
      "slug": "intamin",
      "model_count": 25
    }
  ],
  "ordering_options": [
    {"value": "name", "label": "Name (A-Z)"},
    {"value": "-name", "label": "Name (Z-A)"},
    {"value": "ride_count", "label": "Ride Count (Fewest First)"},
    {"value": "-ride_count", "label": "Ride Count (Most First)"},
    {"value": "created_at", "label": "Date Added (Oldest First)"},
    {"value": "-created_at", "label": "Date Added (Newest First)"}
  ]
}

Ride Model Statistics

// Get global ride model statistics
const response = await fetch('/api/v1/rides/manufacturers/stats/');

// Response (200)
{
  "total_models": 150,
  "total_manufacturers": 45,
  "models_by_category": {
    "RC": 85,
    "DR": 25,
    "FR": 20,
    "WR": 15,
    "TR": 3,
    "OT": 2
  },
  "top_manufacturers": [
    {
      "id": 1,
      "name": "Bolliger & Mabillard",
      "model_count": 15,
      "total_rides": 180
    },
    {
      "id": 2,
      "name": "Intamin",
      "model_count": 25,
      "total_rides": 220
    }
  ],
  "most_popular_models": [
    {
      "id": 5,
      "name": "Inverted Coaster",
      "manufacturer": "Bolliger & Mabillard",
      "ride_count": 45
    },
    {
      "id": 12,
      "name": "Hyper Coaster",
      "manufacturer": "Intamin",
      "ride_count": 38
    }
  ]
}

Ride Model Variants

// Get variants of a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/variants/');

// Response (200)
{
  "count": 3,
  "results": [
    {
      "id": 1,
      "name": "Standard Dive Coaster",
      "description": "Standard 8-across seating configuration",
      "specifications": {
        "seating_configuration": "8-across",
        "typical_height": "200-250 ft",
        "typical_capacity": "1200-1400 riders/hour"
      },
      "ride_count": 8,
      "created_at": "2024-01-01T00:00:00Z"
    },
    {
      "id": 2,
      "name": "Compact Dive Coaster",
      "description": "Smaller footprint with 6-across seating",
      "specifications": {
        "seating_configuration": "6-across",
        "typical_height": "150-200 ft",
        "typical_capacity": "900-1100 riders/hour"
      },
      "ride_count": 4,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Create Ride Model Variant

// Create a new variant for a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/variants/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    name: "Mega Dive Coaster",
    description: "Extra-wide 10-across seating for maximum capacity",
    specifications: {
      "seating_configuration": "10-across",
      "typical_height": "250-300 ft",
      "typical_capacity": "1600-1800 riders/hour"
    }
  })
});

// Success response (201)
{
  "id": 3,
  "name": "Mega Dive Coaster",
  "description": "Extra-wide 10-across seating for maximum capacity",
  "specifications": {
    "seating_configuration": "10-across",
    "typical_height": "250-300 ft",
    "typical_capacity": "1600-1800 riders/hour"
  },
  "ride_count": 0,
  "created_at": "2024-01-28T15:30:00Z"
}

Ride Model Technical Specifications

// Get technical specifications for a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/technical-specs/');

// Response (200)
{
  "count": 5,
  "results": [
    {
      "id": 1,
      "spec_type": "DIMENSIONS",
      "name": "Track Length Range",
      "value": "2500-4000 ft",
      "unit": "feet",
      "description": "Typical track length for dive coasters"
    },
    {
      "id": 2,
      "spec_type": "PERFORMANCE",
      "name": "Maximum Speed",
      "value": "95",
      "unit": "mph",
      "description": "Top speed capability"
    },
    {
      "id": 3,
      "spec_type": "CAPACITY",
      "name": "Theoretical Hourly Capacity",
      "value": "1400",
      "unit": "riders/hour",
      "description": "Maximum theoretical capacity under ideal conditions"
    }
  ]
}

Create Technical Specification

// Add a technical specification to a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/technical-specs/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    spec_type: "SAFETY",
    name: "Block Zones",
    value: "6-8",
    unit: "zones",
    description: "Number of block zones for safe operation"
  })
});

// Success response (201)
{
  "id": 6,
  "spec_type": "SAFETY",
  "name": "Block Zones",
  "value": "6-8",
  "unit": "zones",
  "description": "Number of block zones for safe operation",
  "created_at": "2024-01-28T15:30:00Z"
}

Ride Model Photos

// Get photos for a ride model
const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/photos/');

// Response (200)
{
  "count": 8,
  "results": [
    {
      "id": 150,
      "image_url": "https://imagedelivery.net/account-hash/model123abc/public",
      "image_variants": {
        "thumbnail": "https://imagedelivery.net/account-hash/model123abc/thumbnail",
        "medium": "https://imagedelivery.net/account-hash/model123abc/medium",
        "large": "https://imagedelivery.net/account-hash/model123abc/large",
        "public": "https://imagedelivery.net/account-hash/model123abc/public"
      },
      "caption": "B&M Dive Coaster train design",
      "alt_text": "Wide dive coaster train with 8-across seating",
      "photo_type": "technical",
      "is_primary": true,
      "uploaded_by": {
        "id": 5,
        "username": "coaster_engineer"
      },
      "created_at": "2024-01-15T10:00:00Z"
    }
  ]
}

Upload Ride Model Photo

// Upload a photo to a ride model
const formData = new FormData();
formData.append('image', fileInput.files[0]);
formData.append('caption', 'Technical diagram of dive coaster mechanism');
formData.append('alt_text', 'Detailed technical drawing showing dive coaster holding brake system');
formData.append('photo_type', 'technical');

const response = await fetch('/api/v1/rides/manufacturers/bolliger-mabillard/dive-coaster/photos/', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer your_token_here'
  },
  body: formData
});

// Success response (201)
{
  "id": 151,
  "image_url": "https://imagedelivery.net/account-hash/model456def/public",
  "image_variants": {
    "thumbnail": "https://imagedelivery.net/account-hash/model456def/thumbnail",
    "medium": "https://imagedelivery.net/account-hash/model456def/medium",
    "large": "https://imagedelivery.net/account-hash/model456def/large",
    "public": "https://imagedelivery.net/account-hash/model456def/public"
  },
  "caption": "Technical diagram of dive coaster mechanism",
  "alt_text": "Detailed technical drawing showing dive coaster holding brake system",
  "photo_type": "technical",
  "is_primary": false,
  "is_approved": false,
  "uploaded_by": {
    "id": 1,
    "username": "technical_user"
  },
  "created_at": "2024-01-28T15:30:00Z"
}

Roller Coaster Statistics API

Base Endpoints

GET /api/v1/rides/{ride_id}/stats/
POST /api/v1/rides/{ride_id}/stats/
PUT /api/v1/rides/{ride_id}/stats/
PATCH /api/v1/rides/{ride_id}/stats/
DELETE /api/v1/rides/{ride_id}/stats/

Get Roller Coaster Statistics

// Get detailed statistics for a roller coaster
const response = await fetch('/api/v1/rides/1/stats/');

// Response (200)
{
  "id": 1,
  "height_ft": 205.0,
  "length_ft": 5740.0,
  "speed_mph": 74.0,
  "inversions": 4,
  "ride_time_seconds": 150,
  "track_type": "I-Box Track",
  "track_material": "HYBRID",
  "roller_coaster_type": "SITDOWN",
  "max_drop_height_ft": 200.0,
  "launch_type": "CHAIN",
  "train_style": "Traditional",
  "trains_count": 3,
  "cars_per_train": 6,
  "seats_per_car": 4,
  "ride": {
    "id": 1,
    "name": "Steel Vengeance",
    "slug": "steel-vengeance"
  }
}

Create/Update Roller Coaster Statistics

// Create or update statistics for a roller coaster
const response = await fetch('/api/v1/rides/1/stats/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    height_ft: 205.0,
    length_ft: 5740.0,
    speed_mph: 74.0,
    inversions: 4,
    ride_time_seconds: 150,
    track_material: "HYBRID",
    roller_coaster_type: "SITDOWN",
    launch_type: "CHAIN",
    trains_count: 3,
    cars_per_train: 6,
    seats_per_car: 4
  })
});

// Success response (201)
{
  "id": 1,
  "height_ft": 205.0,
  "length_ft": 5740.0,
  "speed_mph": 74.0,
  "inversions": 4,
  "ride_time_seconds": 150,
  "track_material": "HYBRID",
  "roller_coaster_type": "SITDOWN",
  "launch_type": "CHAIN",
  "trains_count": 3,
  "cars_per_train": 6,
  "seats_per_car": 4,
  "ride": {
    "id": 1,
    "name": "Steel Vengeance",
    "slug": "steel-vengeance"
  }
}

Maps API

Base Endpoints

GET /api/v1/maps/locations/
GET /api/v1/maps/locations/{location_type}/{location_id}/
GET /api/v1/maps/search/
GET /api/v1/maps/bounds/
GET /api/v1/maps/stats/
GET /api/v1/maps/cache/
POST /api/v1/maps/cache/invalidate/

Get Map Locations

// Get all map locations with optional filtering
const response = await fetch('/api/v1/maps/locations/?bounds=40.0,-84.0,42.0,-82.0&search=cedar');

// Response (200)
{
  "count": 25,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "park_1",
      "type": "park",
      "name": "Cedar Point",
      "slug": "cedar-point",
      "latitude": 41.4793,
      "longitude": -82.6833,
      "description": "America's Roller Coast",
      "status": "OPERATING",
      "coaster_count": 17,
      "ride_count": 70,
      "location": {
        "city": "Sandusky",
        "state": "Ohio",
        "country": "United States"
      },
      "primary_photo": {
        "image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
        "image_variants": {
          "thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
          "medium": "https://imagedelivery.net/account-hash/def789ghi012/medium"
        }
      }
    },
    {
      "id": "ride_1",
      "type": "ride",
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "latitude": 41.4801,
      "longitude": -82.6825,
      "description": "Hybrid roller coaster",
      "category": "RC",
      "status": "OPERATING",
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "primary_photo": {
        "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
        "image_variants": {
          "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
          "medium": "https://imagedelivery.net/account-hash/abc123def456/medium"
        }
      }
    }
  ]
}

Get Location Detail

// Get detailed information for a specific location
const response = await fetch('/api/v1/maps/locations/park/1/');

// Response (200)
{
  "id": 1,
  "type": "park",
  "name": "Cedar Point",
  "slug": "cedar-point",
  "latitude": 41.4793,
  "longitude": -82.6833,
  "description": "America's Roller Coast featuring world-class roller coasters",
  "status": "OPERATING",
  "coaster_count": 17,
  "ride_count": 70,
  "average_rating": 4.5,
  "location": {
    "address": "1 Cedar Point Dr",
    "city": "Sandusky",
    "state": "Ohio",
    "country": "United States",
    "postal_code": "44870"
  },
  "operator": {
    "id": 1,
    "name": "Cedar Fair",
    "slug": "cedar-fair"
  },
  "photos": [
    {
      "id": 456,
      "image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
      "image_variants": {
        "thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
        "medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
        "large": "https://imagedelivery.net/account-hash/def789ghi012/large"
      },
      "caption": "Beautiful park entrance"
    }
  ],
  "nearby_locations": [
    {
      "id": "ride_1",
      "type": "ride",
      "name": "Steel Vengeance",
      "distance_miles": 0.2
    }
  ]
}
// Search locations with text query
const response = await fetch('/api/v1/maps/search/?q=roller+coaster&page=1&page_size=20');

// Response (200)
{
  "count": 150,
  "next": "http://localhost:8000/api/v1/maps/search/?page=2",
  "previous": null,
  "results": [
    {
      "id": "ride_1",
      "type": "ride",
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "description": "Hybrid roller coaster",
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "location": {
        "city": "Sandusky",
        "state": "Ohio",
        "country": "United States"
      },
      "match_score": 0.95
    }
  ]
}

Geographic Bounds Query

// Get locations within geographic bounds
const response = await fetch('/api/v1/maps/bounds/?bounds=40.0,-84.0,42.0,-82.0');

// Response (200)
{
  "bounds": {
    "lat_min": 40.0,
    "lng_min": -84.0,
    "lat_max": 42.0,
    "lng_max": -82.0
  },
  "count": 45,
  "locations": [
    {
      "id": "park_1",
      "type": "park",
      "name": "Cedar Point",
      "latitude": 41.4793,
      "longitude": -82.6833,
      "coaster_count": 17
    }
  ]
}

Map Statistics

// Get map service statistics
const response = await fetch('/api/v1/maps/stats/');

// Response (200)
{
  "total_locations": 1250,
  "parks": 125,
  "rides": 1125,
  "countries": 45,
  "cache_hit_rate": 0.85,
  "last_updated": "2024-01-28T15:30:00Z"
}

User Accounts API

Base Endpoints

GET /api/v1/accounts/profiles/
POST /api/v1/accounts/profiles/
GET /api/v1/accounts/profiles/{id}/
PUT /api/v1/accounts/profiles/{id}/
PATCH /api/v1/accounts/profiles/{id}/
DELETE /api/v1/accounts/profiles/{id}/
GET /api/v1/accounts/toplists/
POST /api/v1/accounts/toplists/
GET /api/v1/accounts/toplists/{id}/
PUT /api/v1/accounts/toplists/{id}/
PATCH /api/v1/accounts/toplists/{id}/
DELETE /api/v1/accounts/toplists/{id}/
GET /api/v1/accounts/toplist-items/
POST /api/v1/accounts/toplist-items/

User Profiles

// Get user profile
const response = await fetch('/api/v1/accounts/profiles/1/');

// Response (200)
{
  "id": 1,
  "user": {
    "id": 1,
    "username": "coaster_fan",
    "first_name": "John",
    "last_name": "Doe"
  },
  "bio": "Theme park enthusiast and roller coaster lover",
  "location": "Ohio, USA",
  "website": "https://example.com",
  "avatar": {
    "image_url": "https://imagedelivery.net/account-hash/avatar123/public",
    "image_variants": {
      "thumbnail": "https://imagedelivery.net/account-hash/avatar123/thumbnail",
      "medium": "https://imagedelivery.net/account-hash/avatar123/medium"
    }
  },
  "stats": {
    "parks_visited": 25,
    "rides_ridden": 150,
    "reviews_written": 45,
    "photos_uploaded": 120
  },
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Top Lists

// Get user's top lists
const response = await fetch('/api/v1/accounts/toplists/?user_id=1');

// Response (200)
{
  "count": 3,
  "results": [
    {
      "id": 1,
      "title": "Top 10 Roller Coasters",
      "description": "My favorite roller coasters of all time",
      "is_public": true,
      "user": {
        "id": 1,
        "username": "coaster_fan"
      },
      "item_count": 10,
      "created_at": "2024-01-15T10:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    }
  ]
}

Top List Items

// Get items in a top list
const response = await fetch('/api/v1/accounts/toplist-items/?toplist_id=1');

// Response (200)
{
  "count": 10,
  "results": [
    {
      "id": 1,
      "position": 1,
      "ride": {
        "id": 1,
        "name": "Steel Vengeance",
        "slug": "steel-vengeance",
        "park": {
          "id": 1,
          "name": "Cedar Point",
          "slug": "cedar-point"
        }
      },
      "notes": "Incredible airtime and smooth ride experience",
      "created_at": "2024-01-15T10:00:00Z"
    }
  ]
}

Rankings API

Base Endpoints

GET /api/v1/rankings/
GET /api/v1/rankings/{id}/
POST /api/v1/rankings/calculate/

Get Rankings

// Get current ride rankings
const response = await fetch('/api/v1/rankings/?category=RC&limit=50');

// Response (200)
{
  "count": 500,
  "next": "http://localhost:8000/api/v1/rankings/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "rank": 1,
      "ride": {
        "id": 1,
        "name": "Steel Vengeance",
        "slug": "steel-vengeance",
        "park": {
          "id": 1,
          "name": "Cedar Point",
          "slug": "cedar-point"
        }
      },
      "score": 9.85,
      "average_rating": 4.8,
      "review_count": 1250,
      "category": "RC",
      "last_calculated": "2024-01-28T15:30:00Z"
    }
  ]
}

Trigger Ranking Calculation

// Trigger recalculation of rankings
const response = await fetch('/api/v1/rankings/calculate/', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer your_token_here'
  }
});

// Response (202)
{
  "message": "Ranking calculation triggered",
  "task_id": "abc123def456",
  "estimated_completion": "2024-01-28T16:00:00Z"
}

Base Endpoints

GET /api/v1/trending/content/
GET /api/v1/trending/new/
// Get trending parks and rides
const response = await fetch('/api/v1/trending/content/?timeframe=week&limit=20');

// Response (200)
{
  "timeframe": "week",
  "generated_at": "2024-01-28T15:30:00Z",
  "trending_parks": [
    {
      "id": 1,
      "name": "Cedar Point",
      "slug": "cedar-point",
      "trend_score": 95.5,
      "view_count": 15000,
      "review_count": 45,
      "photo_count": 120,
      "location": {
        "city": "Sandusky",
        "state": "Ohio",
        "country": "United States"
      }
    }
  ],
  "trending_rides": [
    {
      "id": 1,
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "trend_score": 98.2,
      "view_count": 25000,
      "review_count": 85,
      "photo_count": 200,
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      }
    }
  ]
}

Get New Content

// Get recently added content
const response = await fetch('/api/v1/trending/new/?days=7&limit=20');

// Response (200)
{
  "timeframe_days": 7,
  "generated_at": "2024-01-28T15:30:00Z",
  "new_parks": [
    {
      "id": 25,
      "name": "New Theme Park",
      "slug": "new-theme-park",
      "created_at": "2024-01-25T10:00:00Z",
      "location": {
        "city": "Orlando",
        "state": "Florida",
        "country": "United States"
      }
    }
  ],
  "new_rides": [
    {
      "id": 150,
      "name": "Lightning Strike",
      "slug": "lightning-strike",
      "category": "RC",
      "created_at": "2024-01-26T14:30:00Z",
      "park": {
        "id": 5,
        "name": "Adventure Park",
        "slug": "adventure-park"
      }
    }
  ],
  "new_photos": [
    {
      "id": 500,
      "caption": "Amazing sunset view",
      "created_at": "2024-01-27T18:00:00Z",
      "ride": {
        "id": 1,
        "name": "Steel Vengeance",
        "slug": "steel-vengeance"
      }
    }
  ]
}

Stats API

Base Endpoints

GET /api/v1/stats/
POST /api/v1/stats/recalculate/

Get Platform Statistics

// Get comprehensive platform statistics
const response = await fetch('/api/v1/stats/');

// Response (200)
{
  "total_parks": 125,
  "total_rides": 1250,
  "total_manufacturers": 85,
  "total_operators": 150,
  "total_designers": 65,
  "total_property_owners": 45,
  "total_roller_coasters": 800,
  "total_photos": 15000,
  "total_park_photos": 5000,
  "total_ride_photos": 10000,
  "total_reviews": 25000,
  "total_park_reviews": 8000,
  "total_ride_reviews": 17000,
  "operating_parks": 120,
  "operating_rides": 1100,
  "countries_represented": 45,
  "states_represented": 50,
  "average_park_rating": 4.2,
  "average_ride_rating": 4.1,
  "most_popular_manufacturer": {
    "id": 1,
    "name": "Bolliger & Mabillard",
    "ride_count": 150
  },
  "newest_park": {
    "id": 125,
    "name": "Latest Theme Park",
    "opening_date": "2024-01-15"
  },
  "newest_ride": {
    "id": 1250,
    "name": "Latest Coaster",
    "opening_date": "2024-01-20"
  },
  "last_updated": "2024-01-28T15:30:00Z",
  "cache_expires_at": "2024-01-28T16:30:00Z"
}

Recalculate Statistics

// Trigger statistics recalculation
const response = await fetch('/api/v1/stats/recalculate/', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer your_token_here'
  }
});

// Response (202)
{
  "message": "Statistics recalculation triggered",
  "task_id": "stats_calc_abc123",
  "estimated_completion": "2024-01-28T15:35:00Z"
}

Photo Management

Upload Photos

// Upload photo to a ride
const formData = new FormData();
formData.append('image', fileInput.files[0]);
formData.append('caption', 'Amazing ride photo');
formData.append('alt_text', 'Steel Vengeance racing through the structure');
formData.append('photo_type', 'exterior');

const response = await fetch('/api/v1/rides/1/photos/', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer your_token_here'
  },
  body: formData
});

// Success response (201)
{
  "id": 123,
  "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
  "image_variants": {
    "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail",
    "medium": "https://imagedelivery.net/account-hash/abc123def456/medium",
    "large": "https://imagedelivery.net/account-hash/abc123def456/large",
    "public": "https://imagedelivery.net/account-hash/abc123def456/public"
  },
  "caption": "Amazing ride photo",
  "alt_text": "Steel Vengeance racing through the structure",
  "photo_type": "exterior",
  "is_primary": false,
  "is_approved": false,
  "uploaded_by": {
    "id": 1,
    "username": "photographer"
  },
  "created_at": "2024-01-28T15:30:00Z"
}

Set Banner and Card Images

// Set banner and card images for a ride
const response = await fetch('/api/v1/rides/1/image-settings/', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    banner_image_id: 123,
    card_image_id: 124
  })
});

// Success response (200)
{
  "banner_image": {
    "id": 123,
    "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
    "caption": "Amazing ride photo"
  },
  "card_image": {
    "id": 124,
    "image_url": "https://imagedelivery.net/account-hash/def456ghi789/public",
    "caption": "Another great photo"
  },
  "message": "Image settings updated successfully"
}

Photo Types

Available photo types for rides and parks:

  • exterior - External view of the ride/park
  • interior - Internal view (for dark rides, stations, etc.)
  • action - Ride in motion
  • construction - Construction/installation photos
  • aerial - Aerial/drone photography
  • detail - Close-up details
  • queue - Queue line photos
  • station - Station area
  • other - Other types

Search and Autocomplete

// Search companies for autocomplete
const response = await fetch('/api/v1/rides/search/companies/?q=bolliger&role=manufacturer');

// Response (200)
{
  "results": [
    {
      "id": 1,
      "name": "Bolliger & Mabillard",
      "slug": "bolliger-mabillard",
      "roles": ["MANUFACTURER", "DESIGNER"],
      "ride_count": 150,
      "park_count": 0
    }
  ],
  "query": "bolliger",
  "count": 1
}

Ride Model Search

// Search ride models for autocomplete
const response = await fetch('/api/v1/rides/search/ride-models/?q=dive&manufacturer_id=1');

// Response (200)
{
  "results": [
    {
      "id": 1,
      "name": "Dive Coaster",
      "slug": "dive-coaster",
      "manufacturer": {
        "id": 1,
        "name": "Bolliger & Mabillard",
        "slug": "bolliger-mabillard"
      },
      "ride_count": 12,
      "category": "RC"
    }
  ],
  "query": "dive",
  "count": 1
}

Search Suggestions

// Get search suggestions for ride search box
const response = await fetch('/api/v1/rides/search-suggestions/?q=steel');

// Response (200)
{
  "suggestions": [
    {
      "type": "ride",
      "id": 1,
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "park": "Cedar Point",
      "match_type": "name"
    },
    {
      "type": "ride",
      "id": 5,
      "name": "Steel Force",
      "slug": "steel-force", 
      "park": "Dorney Park",
      "match_type": "name"
    },
    {
      "type": "material",
      "value": "STEEL",
      "label": "Steel Track Material",
      "match_type": "filter"
    }
  ],
  "query": "steel",
  "count": 3
}

Core Entity Search API

Base Endpoints

POST /api/v1/core/entities/search/
POST /api/v1/core/entities/not-found/
GET /api/v1/core/entities/suggestions/
// Perform fuzzy entity search with authentication prompts
const response = await fetch('/api/v1/core/entities/search/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here' // Optional
  },
  body: JSON.stringify({
    query: "cedar point",
    entity_types: ["park", "ride", "company"], // Optional
    include_suggestions: true // Optional, default true
  })
});

// Success response (200)
{
  "success": true,
  "query": "cedar point",
  "matches": [
    {
      "entity_type": "park",
      "name": "Cedar Point",
      "slug": "cedar-point",
      "score": 0.95,
      "confidence": "high",
      "match_reason": "Exact name match with 'Cedar Point'",
      "url": "/parks/cedar-point/",
      "entity_id": 1
    }
  ],
  "suggestion": {
    "suggested_name": "Cedar Point",
    "entity_type": "park",
    "requires_authentication": false,
    "login_prompt": "Log in to suggest adding a new park",
    "signup_prompt": "Sign up to contribute to ThrillWiki",
    "creation_hint": "Help expand ThrillWiki by adding missing parks"
  },
  "user_authenticated": true
}

// No matches found response (200)
{
  "success": true,
  "query": "nonexistent park",
  "matches": [],
  "suggestion": {
    "suggested_name": "Nonexistent Park",
    "entity_type": "park",
    "requires_authentication": true,
    "login_prompt": "Log in to suggest adding 'Nonexistent Park'",
    "signup_prompt": "Sign up to help expand ThrillWiki",
    "creation_hint": "Can't find what you're looking for? Help us add it!"
  },
  "user_authenticated": false
}

// Error response (400)
{
  "success": false,
  "error": "Query must be at least 2 characters long",
  "code": "INVALID_QUERY"
}

Entity Not Found Handler

// Handle entity not found scenarios with context
const response = await fetch('/api/v1/core/entities/not-found/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    original_query: "steel vengance", // User's original search
    attempted_slug: "steel-vengance", // Optional: slug that failed
    entity_type: "ride", // Optional: hint about what they were looking for
    context: { // Optional: additional context
      park_slug: "cedar-point",
      source_page: "park_detail"
    }
  })
});

// Response with suggestions (200)
{
  "success": true,
  "original_query": "steel vengance",
  "attempted_slug": "steel-vengance",
  "context": {
    "park_slug": "cedar-point",
    "source_page": "park_detail"
  },
  "matches": [
    {
      "entity_type": "ride",
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "score": 0.92,
      "confidence": "high",
      "match_reason": "Similar spelling to 'Steel Vengance'",
      "url": "/parks/cedar-point/rides/steel-vengeance/",
      "entity_id": 1
    }
  ],
  "has_matches": true,
  "suggestion": {
    "suggested_name": "Steel Vengance",
    "entity_type": "ride",
    "requires_authentication": true,
    "login_prompt": "Log in to suggest adding 'Steel Vengance'",
    "signup_prompt": "Sign up to contribute ride information",
    "creation_hint": "Did you mean 'Steel Vengeance'? Or help us add a new ride!"
  },
  "user_authenticated": false
}

Quick Entity Suggestions

// Get lightweight suggestions for autocomplete
const response = await fetch('/api/v1/core/entities/suggestions/?q=steel&types=park,ride&limit=5');

// Response (200)
{
  "suggestions": [
    {
      "name": "Steel Vengeance",
      "type": "ride",
      "slug": "steel-vengeance",
      "url": "/parks/cedar-point/rides/steel-vengeance/",
      "score": 0.95,
      "confidence": "high"
    },
    {
      "name": "Steel Force",
      "type": "ride",
      "slug": "steel-force",
      "url": "/parks/dorney-park/rides/steel-force/",
      "score": 0.90,
      "confidence": "high"
    },
    {
      "name": "Steel Phantom",
      "type": "ride",
      "slug": "steel-phantom",
      "url": "/parks/kennywood/rides/steel-phantom/",
      "score": 0.85,
      "confidence": "medium"
    }
  ],
  "query": "steel",
  "count": 3
}

// Error handling (200 - always returns 200 for autocomplete)
{
  "suggestions": [],
  "query": "x",
  "error": "Query too short"
}

Entity Types

Available entity types for search:

  • park - Theme parks and amusement parks
  • ride - Individual rides and attractions
  • company - Manufacturers, operators, designers

Confidence Levels

  • high - Score >= 0.8, very likely match
  • medium - Score 0.6-0.79, probable match
  • low - Score 0.4-0.59, possible match
  • very_low - Score < 0.4, unlikely match

Use Cases

404 Page Integration

// When a page is not found, suggest alternatives
async function handle404(originalPath) {
  const pathParts = originalPath.split('/');
  const entityType = pathParts[1]; // 'parks' or 'rides'
  const slug = pathParts[pathParts.length - 1];
  
  const response = await fetch('/api/v1/core/entities/not-found/', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      original_query: slug.replace(/-/g, ' '),
      attempted_slug: slug,
      entity_type: entityType.slice(0, -1), // Remove 's'
      context: { source_page: '404' }
    })
  });
  
  const data = await response.json();
  return data.matches; // Show suggestions to user
}

Search Box Autocomplete

// Implement search autocomplete with debouncing
const searchInput = document.getElementById('search');
let searchTimeout;

searchInput.addEventListener('input', (e) => {
  clearTimeout(searchTimeout);
  const query = e.target.value.trim();
  
  if (query.length < 2) {
    hideAutocomplete();
    return;
  }
  
  searchTimeout = setTimeout(async () => {
    const response = await fetch(
      `/api/v1/core/entities/suggestions/?q=${encodeURIComponent(query)}&limit=8`
    );
    const data = await response.json();
    showAutocomplete(data.suggestions);
  }, 300);
});

Smart Search Results

// Enhanced search with fuzzy matching and suggestions
async function performSearch(query) {
  const response = await fetch('/api/v1/core/entities/search/', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: query,
      entity_types: ['park', 'ride', 'company'],
      include_suggestions: true
    })
  });
  
  const data = await response.json();
  
  if (data.matches.length > 0) {
    // Show search results
    displaySearchResults(data.matches);
  } else if (data.suggestion) {
    // Show "not found" page with suggestions and creation prompts
    displayNotFound(data.suggestion, data.user_authenticated);
  }
}

Health Check API

Base Endpoints

GET /api/v1/health/
GET /api/v1/health/simple/
GET /api/v1/health/performance/

Comprehensive Health Check

// Get detailed system health information
const response = await fetch('/api/v1/health/');

// Response (200)
{
  "status": "healthy",
  "timestamp": "2024-01-28T15:30:00Z",
  "version": "1.0.0",
  "environment": "production",
  "database": {
    "status": "healthy",
    "response_time_ms": 15,
    "connections": {
      "active": 5,
      "max": 100
    }
  },
  "cache": {
    "status": "healthy",
    "response_time_ms": 2,
    "hit_rate": 0.85
  },
  "storage": {
    "status": "healthy",
    "cloudflare_images": "connected"
  },
  "external_services": {
    "maps_api": "healthy",
    "email_service": "healthy"
  },
  "performance": {
    "avg_response_time_ms": 125,
    "requests_per_minute": 450,
    "error_rate": 0.001
  }
}

Simple Health Check

// Get basic health status
const response = await fetch('/api/v1/health/simple/');

// Response (200)
{
  "status": "ok",
  "timestamp": "2024-01-28T15:30:00Z"
}

Performance Metrics

// Get detailed performance metrics
const response = await fetch('/api/v1/health/performance/');

// Response (200)
{
  "timestamp": "2024-01-28T15:30:00Z",
  "response_times": {
    "avg_ms": 125,
    "p50_ms": 95,
    "p95_ms": 250,
    "p99_ms": 500
  },
  "throughput": {
    "requests_per_second": 45.5,
    "requests_per_minute": 2730
  },
  "error_rates": {
    "total_error_rate": 0.001,
    "4xx_error_rate": 0.0008,
    "5xx_error_rate": 0.0002
  },
  "resource_usage": {
    "cpu_percent": 25.5,
    "memory_percent": 45.2,
    "disk_usage_percent": 60.1
  }
}

Email API

Base Endpoints

GET /api/v1/email/
POST /api/v1/email/send/
GET /api/v1/email/templates/
POST /api/v1/email/templates/

Send Email

// Send email notification
const response = await fetch('/api/v1/email/send/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    to: 'user@example.com',
    template: 'welcome',
    context: {
      username: 'John Doe',
      park_name: 'Cedar Point'
    }
  })
});

// Success response (202)
{
  "message": "Email queued for delivery",
  "email_id": "email_abc123def456",
  "estimated_delivery": "2024-01-28T15:32:00Z"
}

Get Email Templates

// Get available email templates
const response = await fetch('/api/v1/email/templates/');

// Response (200)
{
  "templates": [
    {
      "id": "welcome",
      "name": "Welcome Email",
      "description": "Welcome new users to ThrillWiki",
      "variables": ["username", "verification_link"]
    },
    {
      "id": "password_reset",
      "name": "Password Reset",
      "description": "Password reset instructions",
      "variables": ["username", "reset_link", "expiry_time"]
    },
    {
      "id": "ride_notification",
      "name": "New Ride Notification",
      "description": "Notify users of new rides at their favorite parks",
      "variables": ["username", "ride_name", "park_name", "opening_date"]
    }
  ]
}

History API

Base Endpoints

GET /api/v1/history/timeline/
GET /api/v1/history/parks/{park_slug}/
GET /api/v1/history/parks/{park_slug}/detail/
GET /api/v1/history/parks/{park_slug}/rides/{ride_slug}/
GET /api/v1/history/parks/{park_slug}/rides/{ride_slug}/detail/

Get Unified Timeline

// Get unified timeline of all changes across the platform
const response = await fetch('/api/v1/history/timeline/?limit=50&days=7');

// Response (200)
{
  "count": 125,
  "next": "http://localhost:8000/api/v1/history/timeline/?page=2",
  "previous": null,
  "results": [
    {
      "id": 150,
      "object_type": "ride",
      "object_id": 25,
      "object_name": "Lightning Strike",
      "change_type": "CREATE",
      "changed_at": "2024-01-27T14:30:00Z",
      "changed_by": {
        "id": 5,
        "username": "contributor",
        "display_name": "Theme Park Contributor"
      },
      "summary": "New ride added to Adventure Park",
      "park_context": {
        "id": 15,
        "name": "Adventure Park",
        "slug": "adventure-park"
      },
      "fields_changed": [
        {
          "field": "name",
          "old_value": null,
          "new_value": "Lightning Strike"
        },
        {
          "field": "category",
          "old_value": null,
          "new_value": "RC"
        }
      ]
    },
    {
      "id": 149,
      "object_type": "park",
      "object_id": 10,
      "object_name": "Magic Kingdom",
      "change_type": "UPDATE",
      "changed_at": "2024-01-27T12:15:00Z",
      "changed_by": {
        "id": 3,
        "username": "park_editor",
        "display_name": "Park Information Editor"
      },
      "summary": "Updated operating hours and website",
      "fields_changed": [
        {
          "field": "website",
          "old_value": "https://old-website.com",
          "new_value": "https://new-website.com"
        },
        {
          "field": "operating_season",
          "old_value": "Year Round",
          "new_value": "March - December"
        }
      ]
    }
  ]
}

Get Park History

// Get change history for a specific park
const response = await fetch('/api/v1/history/parks/cedar-point/');

// Response (200)
{
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "total_changes": 45,
  "changes": [
    {
      "id": 25,
      "change_type": "UPDATE",
      "changed_at": "2024-01-25T10:00:00Z",
      "changed_by": {
        "id": 2,
        "username": "park_admin",
        "display_name": "Park Administrator"
      },
      "fields_changed": [
        {
          "field": "coaster_count",
          "old_value": 16,
          "new_value": 17,
          "display_name": "Roller Coaster Count"
        }
      ],
      "change_reason": "Added new roller coaster Steel Vengeance",
      "related_objects": [
        {
          "type": "ride",
          "id": 1,
          "name": "Steel Vengeance",
          "action": "created"
        }
      ]
    }
  ]
}

Get Park History Detail

// Get detailed park history with full context
const response = await fetch('/api/v1/history/parks/cedar-point/detail/');

// Response (200)
{
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point",
    "current_status": "OPERATING"
  },
  "history_summary": {
    "total_changes": 45,
    "first_recorded": "2024-01-01T00:00:00Z",
    "last_updated": "2024-01-28T15:30:00Z",
    "major_milestones": [
      {
        "date": "2018-05-05",
        "event": "Steel Vengeance opened",
        "significance": "New record-breaking hybrid coaster"
      },
      {
        "date": "2016-05-07",
        "event": "Valravn opened",
        "significance": "First dive coaster at Cedar Point"
      }
    ]
  },
  "detailed_changes": [
    {
      "id": 25,
      "change_type": "UPDATE",
      "changed_at": "2024-01-25T10:00:00Z",
      "changed_by": {
        "id": 2,
        "username": "park_admin",
        "display_name": "Park Administrator"
      },
      "fields_changed": [
        {
          "field": "coaster_count",
          "old_value": 16,
          "new_value": 17,
          "display_name": "Roller Coaster Count"
        }
      ],
      "change_reason": "Added new roller coaster Steel Vengeance",
      "related_objects": [
        {
          "type": "ride",
          "id": 1,
          "name": "Steel Vengeance",
          "action": "created"
        }
      ]
    }
  ]
}

Get Ride History

// Get change history for a specific ride
const response = await fetch('/api/v1/history/parks/cedar-point/rides/steel-vengeance/');

// Response (200)
{
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "ride": {
    "id": 1,
    "name": "Steel Vengeance",
    "slug": "steel-vengeance"
  },
  "total_changes": 12,
  "changes": [
    {
      "id": 35,
      "change_type": "UPDATE",
      "changed_at": "2024-01-20T14:00:00Z",
      "changed_by": {
        "id": 3,
        "username": "ride_operator",
        "display_name": "Ride Operations Manager"
      },
      "fields_changed": [
        {
          "field": "status",
          "old_value": "CLOSED_TEMP",
          "new_value": "OPERATING",
          "display_name": "Operating Status"
        }
      ],
      "change_reason": "Maintenance completed, ride reopened"
    },
    {
      "id": 30,
      "change_type": "CREATE",
      "changed_at": "2018-05-05T09:00:00Z",
      "changed_by": {
        "id": 1,
        "username": "system_admin",
        "display_name": "System Administrator"
      },
      "fields_changed": [],
      "change_reason": "Initial ride creation for opening day"
    }
  ]
}

Get Ride History Detail

// Get detailed ride history with comprehensive context
const response = await fetch('/api/v1/history/parks/cedar-point/rides/steel-vengeance/detail/');

// Response (200)
{
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "ride": {
    "id": 1,
    "name": "Steel Vengeance",
    "slug": "steel-vengeance",
    "current_status": "OPERATING",
    "category": "RC"
  },
  "history_summary": {
    "total_changes": 12,
    "first_recorded": "2018-05-05T09:00:00Z",
    "last_updated": "2024-01-28T15:30:00Z",
    "status_changes": [
      {
        "date": "2018-05-05",
        "from_status": null,
        "to_status": "OPERATING",
        "reason": "Grand opening"
      },
      {
        "date": "2023-10-31",
        "from_status": "OPERATING",
        "to_status": "CLOSED_TEMP",
        "reason": "End of season maintenance"
      },
      {
        "date": "2024-05-01",
        "from_status": "CLOSED_TEMP",
        "to_status": "OPERATING",
        "reason": "Season reopening"
      }
    ],
    "major_updates": [
      {
        "date": "2019-03-15",
        "description": "Updated safety systems and block zones",
        "impact": "Improved capacity and safety"
      },
      {
        "date": "2021-06-10",
        "description": "Track retracking in select sections",
        "impact": "Enhanced ride smoothness"
      }
    ]
  },
  "detailed_changes": [
    {
      "id": 35,
      "change_type": "UPDATE",
      "changed_at": "2024-01-20T14:00:00Z",
      "changed_by": {
        "id": 3,
        "username": "ride_operator",
        "display_name": "Ride Operations Manager"
      },
      "fields_changed": [
        {
          "field": "status",
          "old_value": "CLOSED_TEMP",
          "new_value": "OPERATING",
          "display_name": "Operating Status"
        },
        {
          "field": "capacity_per_hour",
          "old_value": 1200,
          "new_value": 1250,
          "display_name": "Hourly Capacity"
        }
      ],
      "change_reason": "Maintenance completed, ride reopened with improved capacity",
      "technical_notes": "Block zone timing optimized during maintenance period"
    }
  ]
}

Companies API

Base Endpoints

GET /api/v1/companies/
POST /api/v1/companies/
GET /api/v1/companies/{id}/
PUT /api/v1/companies/{id}/
PATCH /api/v1/companies/{id}/
DELETE /api/v1/companies/{id}/
GET /api/v1/companies/search/
GET /api/v1/companies/filter-options/
GET /api/v1/companies/stats/
GET /api/v1/companies/{id}/parks/
GET /api/v1/companies/{id}/rides/
GET /api/v1/companies/{id}/ride-models/

List Companies

// Get companies with filtering
const response = await fetch('/api/v1/companies/?search=cedar&roles=OPERATOR&country=United States');

// Response (200)
{
  "count": 15,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "Cedar Fair",
      "slug": "cedar-fair",
      "roles": ["OPERATOR", "PROPERTY_OWNER"],
      "founded_year": 1983,
      "headquarters": {
        "city": "Sandusky",
        "state": "Ohio",
        "country": "United States"
      },
      "website": "https://cedarfair.com",
      "park_count": 12,
      "ride_count": 0,
      "ride_model_count": 0,
      "description": "Leading amusement park operator in North America",
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    }
  ]
}

Company Detail

// Get detailed company information
const response = await fetch('/api/v1/companies/1/');

// Response (200)
{
  "id": 1,
  "name": "Cedar Fair",
  "slug": "cedar-fair",
  "roles": ["OPERATOR", "PROPERTY_OWNER"],
  "founded_year": 1983,
  "headquarters": {
    "address": "1 Cedar Point Dr",
    "city": "Sandusky",
    "state": "Ohio",
    "country": "United States",
    "postal_code": "44870"
  },
  "website": "https://cedarfair.com",
  "description": "Leading amusement park operator in North America with a portfolio of premier parks",
  "park_count": 12,
  "ride_count": 0,
  "ride_model_count": 0,
  "notable_parks": [
    {
      "id": 1,
      "name": "Cedar Point",
      "slug": "cedar-point",
      "coaster_count": 17
    },
    {
      "id": 3,
      "name": "Canada's Wonderland",
      "slug": "canadas-wonderland",
      "coaster_count": 17
    }
  ],
  "financial_info": {
    "stock_symbol": "FUN",
    "market_cap": "2.8B USD",
    "annual_revenue": "1.4B USD"
  },
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Create Company

// Create a new company
const response = await fetch('/api/v1/companies/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    name: "New Theme Park Company",
    roles: ["OPERATOR"],
    founded_year: 2024,
    headquarters: {
      city: "Orlando",
      state: "Florida",
      country: "United States"
    },
    website: "https://newthemeparkco.com",
    description: "Innovative new theme park operator"
  })
});

// Success response (201)
{
  "id": 25,
  "name": "New Theme Park Company",
  "slug": "new-theme-park-company",
  "roles": ["OPERATOR"],
  "founded_year": 2024,
  "headquarters": {
    "city": "Orlando",
    "state": "Florida",
    "country": "United States"
  },
  "website": "https://newthemeparkco.com",
  "description": "Innovative new theme park operator",
  "park_count": 0,
  "ride_count": 0,
  "ride_model_count": 0,
  "created_at": "2024-01-28T15:30:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Company Search

// Search companies with advanced filtering
const response = await fetch('/api/v1/companies/search/?q=intamin&roles=MANUFACTURER&min_ride_count=50');

// Response (200)
{
  "count": 3,
  "results": [
    {
      "id": 5,
      "name": "Intamin",
      "slug": "intamin",
      "roles": ["MANUFACTURER", "DESIGNER"],
      "headquarters": {
        "city": "Schaan",
        "country": "Liechtenstein"
      },
      "ride_count": 250,
      "ride_model_count": 35,
      "match_score": 0.95
    }
  ]
}

Company Filter Options

// Get filter options for companies
const response = await fetch('/api/v1/companies/filter-options/');

// Response (200)
{
  "roles": [
    ["MANUFACTURER", "Manufacturer"],
    ["OPERATOR", "Park Operator"],
    ["DESIGNER", "Ride Designer"],
    ["PROPERTY_OWNER", "Property Owner"]
  ],
  "countries": [
    "United States",
    "Germany",
    "Switzerland",
    "United Kingdom",
    "Japan",
    "Netherlands"
  ],
  "founded_year_range": {
    "min": 1850,
    "max": 2024
  },
  "ordering_options": [
    {"value": "name", "label": "Name (A-Z)"},
    {"value": "-name", "label": "Name (Z-A)"},
    {"value": "founded_year", "label": "Founded Year (Oldest First)"},
    {"value": "-founded_year", "label": "Founded Year (Newest First)"},
    {"value": "park_count", "label": "Park Count (Fewest First)"},
    {"value": "-park_count", "label": "Park Count (Most First)"},
    {"value": "ride_count", "label": "Ride Count (Fewest First)"},
    {"value": "-ride_count", "label": "Ride Count (Most First)"}
  ]
}

Company Statistics

// Get company statistics
const response = await fetch('/api/v1/companies/stats/');

// Response (200)
{
  "total_companies": 150,
  "by_role": {
    "MANUFACTURER": 45,
    "OPERATOR": 65,
    "DESIGNER": 35,
    "PROPERTY_OWNER": 25
  },
  "by_country": {
    "United States": 45,
    "Germany": 25,
    "Switzerland": 15,
    "United Kingdom": 12,
    "Japan": 8
  },
  "top_manufacturers": [
    {
      "id": 1,
      "name": "Bolliger & Mabillard",
      "ride_count": 180,
      "ride_model_count": 15
    },
    {
      "id": 5,
      "name": "Intamin",
      "ride_count": 250,
      "ride_model_count": 35
    }
  ],
  "top_operators": [
    {
      "id": 10,
      "name": "Six Flags",
      "park_count": 27,
      "total_rides": 450
    },
    {
      "id": 1,
      "name": "Cedar Fair",
      "park_count": 12,
      "total_rides": 280
    }
  ]
}

Company Parks

// Get parks operated by a company
const response = await fetch('/api/v1/companies/1/parks/');

// Response (200)
{
  "company": {
    "id": 1,
    "name": "Cedar Fair",
    "slug": "cedar-fair"
  },
  "count": 12,
  "parks": [
    {
      "id": 1,
      "name": "Cedar Point",
      "slug": "cedar-point",
      "status": "OPERATING",
      "coaster_count": 17,
      "ride_count": 70,
      "location": {
        "city": "Sandusky",
        "state": "Ohio",
        "country": "United States"
      }
    },
    {
      "id": 3,
      "name": "Canada's Wonderland",
      "slug": "canadas-wonderland",
      "status": "OPERATING",
      "coaster_count": 17,
      "ride_count": 65,
      "location": {
        "city": "Vaughan",
        "state": "Ontario",
        "country": "Canada"
      }
    }
  ]
}

Company Rides

// Get rides manufactured by a company
const response = await fetch('/api/v1/companies/5/rides/?role=MANUFACTURER');

// Response (200)
{
  "company": {
    "id": 5,
    "name": "Intamin",
    "slug": "intamin"
  },
  "role": "MANUFACTURER",
  "count": 250,
  "rides": [
    {
      "id": 15,
      "name": "Millennium Force",
      "slug": "millennium-force",
      "category": "RC",
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "opening_date": "2000-05-13"
    },
    {
      "id": 25,
      "name": "Top Thrill Dragster",
      "slug": "top-thrill-dragster",
      "category": "RC",
      "park": {
        "id": 1,
        "name": "Cedar Point",
        "slug": "cedar-point"
      },
      "opening_date": "2003-05-04"
    }
  ]
}

Company Ride Models

// Get ride models created by a manufacturer
const response = await fetch('/api/v1/companies/5/ride-models/');

// Response (200)
{
  "company": {
    "id": 5,
    "name": "Intamin",
    "slug": "intamin"
  },
  "count": 35,
  "ride_models": [
    {
      "id": 10,
      "name": "Giga Coaster",
      "slug": "giga-coaster",
      "category": "RC",
      "ride_count": 8,
      "description": "Ultra-tall roller coaster over 300 feet"
    },
    {
      "id": 12,
      "name": "Accelerator Coaster",
      "slug": "accelerator-coaster",
      "category": "RC",
      "ride_count": 15,
      "description": "Hydraulic launch coaster with rapid acceleration"
    }
  ]
}

Park Areas API

Base Endpoints

GET /api/v1/parks/{park_id}/areas/
POST /api/v1/parks/{park_id}/areas/
GET /api/v1/parks/{park_id}/areas/{id}/
PUT /api/v1/parks/{park_id}/areas/{id}/
PATCH /api/v1/parks/{park_id}/areas/{id}/
DELETE /api/v1/parks/{park_id}/areas/{id}/

List Park Areas

// Get areas within a park
const response = await fetch('/api/v1/parks/1/areas/');

// Response (200)
{
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "count": 8,
  "results": [
    {
      "id": 1,
      "name": "Frontier Town",
      "slug": "frontier-town",
      "description": "Wild West themed area featuring wooden coasters and western attractions",
      "theme": "Wild West",
      "opening_date": "1971-05-01",
      "ride_count": 12,
      "coaster_count": 3,
      "notable_rides": [
        {
          "id": 1,
          "name": "Steel Vengeance",
          "slug": "steel-vengeance"
        },
        {
          "id": 8,
          "name": "Mine Ride",
          "slug": "mine-ride"
        }
      ],
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    },
    {
      "id": 2,
      "name": "Millennium Island",
      "slug": "millennium-island",
      "description": "Home to Millennium Force and other high-thrill attractions",
      "theme": "Futuristic",
      "opening_date": "2000-05-13",
      "ride_count": 5,
      "coaster_count": 2,
      "notable_rides": [
        {
          "id": 15,
          "name": "Millennium Force",
          "slug": "millennium-force"
        }
      ],
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-28T15:30:00Z"
    }
  ]
}

Park Area Detail

// Get detailed information about a park area
const response = await fetch('/api/v1/parks/1/areas/1/');

// Response (200)
{
  "id": 1,
  "name": "Frontier Town",
  "slug": "frontier-town",
  "description": "Wild West themed area featuring wooden coasters and western attractions",
  "theme": "Wild West",
  "opening_date": "1971-05-01",
  "closing_date": null,
  "size_acres": 25.5,
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "ride_count": 12,
  "coaster_count": 3,
  "rides": [
    {
      "id": 1,
      "name": "Steel Vengeance",
      "slug": "steel-vengeance",
      "category": "RC",
      "status": "OPERATING"
    },
    {
      "id": 8,
      "name": "Mine Ride",
      "slug": "mine-ride",
      "category": "RC",
      "status": "OPERATING"
    },
    {
      "id": 25,
      "name": "Thunder Canyon",
      "slug": "thunder-canyon",
      "category": "WR",
      "status": "OPERATING"
    }
  ],
  "dining_locations": [
    {
      "name": "Frontier Inn",
      "type": "Restaurant",
      "cuisine": "American"
    },
    {
      "name": "Chuck Wagon",
      "type": "Quick Service",
      "cuisine": "BBQ"
    }
  ],
  "shops": [
    {
      "name": "Frontier Trading Post",
      "type": "Gift Shop",
      "specialty": "Western themed merchandise"
    }
  ],
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Create Park Area

// Create a new area within a park
const response = await fetch('/api/v1/parks/1/areas/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    name: "Future World",
    description: "Futuristic themed area with high-tech attractions",
    theme: "Futuristic",
    opening_date: "2025-05-01",
    size_acres: 15.0
  })
});

// Success response (201)
{
  "id": 9,
  "name": "Future World",
  "slug": "future-world",
  "description": "Futuristic themed area with high-tech attractions",
  "theme": "Futuristic",
  "opening_date": "2025-05-01",
  "size_acres": 15.0,
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "ride_count": 0,
  "coaster_count": 0,
  "created_at": "2024-01-28T15:30:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Reviews API

Base Endpoints

GET /api/v1/reviews/
POST /api/v1/reviews/
GET /api/v1/reviews/{id}/
PUT /api/v1/reviews/{id}/
PATCH /api/v1/reviews/{id}/
DELETE /api/v1/reviews/{id}/
GET /api/v1/parks/{park_id}/reviews/
POST /api/v1/parks/{park_id}/reviews/
GET /api/v1/rides/{ride_id}/reviews/
POST /api/v1/rides/{ride_id}/reviews/
GET /api/v1/reviews/stats/

List Reviews

// Get reviews with filtering
const response = await fetch('/api/v1/reviews/?content_type=ride&min_rating=4&ordering=-created_at');

// Response (200)
{
  "count": 150,
  "next": "http://localhost:8000/api/v1/reviews/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "rating": 5,
      "title": "Absolutely incredible ride!",
      "content": "Steel Vengeance is hands down the best roller coaster I've ever ridden. The airtime is insane and the ride is incredibly smooth for a hybrid coaster.",
      "content_type": "ride",
      "content_object": {
        "id": 1,
        "name": "Steel Vengeance",
        "slug": "steel-vengeance",
        "park": {
          "id": 1,
          "name": "Cedar Point",
          "slug": "cedar-point"
        }
      },
      "author": {
        "id": 5,
        "username": "coaster_enthusiast",
        "display_name": "Coaster Enthusiast"
      },
      "helpful_count": 25,
      "is_verified": true,
      "visit_date": "2024-01-15",
      "created_at": "2024-01-16T10:30:00Z",
      "updated_at": "2024-01-16T10:30:00Z"
    }
  ]
}

Review Detail

// Get detailed review information
const response = await fetch('/api/v1/reviews/1/');

// Response (200)
{
  "id": 1,
  "rating": 5,
  "title": "Absolutely incredible ride!",
  "content": "Steel Vengeance is hands down the best roller coaster I've ever ridden. The airtime is insane and the ride is incredibly smooth for a hybrid coaster. The theming in Frontier Town really adds to the experience. I waited about 45 minutes but it was absolutely worth it. Can't wait to ride it again!",
  "content_type": "ride",
  "content_object": {
    "id": 1,
    "name": "Steel Vengeance",
    "slug": "steel-vengeance",
    "category": "RC",
    "park": {
      "id": 1,
      "name": "Cedar Point",
      "slug": "cedar-point"
    }
  },
  "author": {
    "id": 5,
    "username": "coaster_enthusiast",
    "display_name": "Coaster Enthusiast",
    "avatar": {
      "image_url": "https://imagedelivery.net/account-hash/avatar123/public"
    }
  },
  "helpful_count": 25,
  "not_helpful_count": 2,
  "is_verified": true,
  "visit_date": "2024-01-15",
  "wait_time_minutes": 45,
  "ride_experience": {
    "front_row": true,
    "weather_conditions": "Sunny",
    "crowd_level": "Moderate"
  },
  "pros": [
    "Incredible airtime",
    "Smooth ride experience",
    "Great theming"
  ],
  "cons": [
    "Long wait times",
    "Can be intense for some riders"
  ],
  "created_at": "2024-01-16T10:30:00Z",
  "updated_at": "2024-01-16T10:30:00Z"
}

Create Review

// Create a new review for a ride
const response = await fetch('/api/v1/rides/1/reviews/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    rating: 4,
    title: "Great coaster with amazing airtime",
    content: "Really enjoyed this ride! The airtime hills are fantastic and the hybrid track provides a smooth experience. Only downside was the long wait time.",
    visit_date: "2024-01-20",
    wait_time_minutes: 60,
    ride_experience: {
      "front_row": false,
      "weather_conditions": "Cloudy",
      "crowd_level": "Busy"
    },
    pros: ["Amazing airtime", "Smooth ride", "Great layout"],
    cons: ["Long wait times", "Can be intimidating"]
  })
});

// Success response (201)
{
  "id": 25,
  "rating": 4,
  "title": "Great coaster with amazing airtime",
  "content": "Really enjoyed this ride! The airtime hills are fantastic and the hybrid track provides a smooth experience. Only downside was the long wait time.",
  "content_type": "ride",
  "content_object": {
    "id": 1,
    "name": "Steel Vengeance",
    "slug": "steel-vengeance"
  },
  "author": {
    "id": 1,
    "username": "current_user",
    "display_name": "Current User"
  },
  "helpful_count": 0,
  "not_helpful_count": 0,
  "is_verified": false,
  "visit_date": "2024-01-20",
  "wait_time_minutes": 60,
  "ride_experience": {
    "front_row": false,
    "weather_conditions": "Cloudy",
    "crowd_level": "Busy"
  },
  "pros": ["Amazing airtime", "Smooth ride", "Great layout"],
  "cons": ["Long wait times", "Can be intimidating"],
  "created_at": "2024-01-28T15:30:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

Park Reviews

// Get reviews for a specific park
const response = await fetch('/api/v1/parks/1/reviews/?ordering=-rating');

// Response (200)
{
  "park": {
    "id": 1,
    "name": "Cedar Point",
    "slug": "cedar-point"
  },
  "count": 85,
  "results": [
    {
      "id": 15,
      "rating": 5,
      "title": "Best theme park in the world!",
      "content": "Cedar Point truly lives up to its reputation as America's Roller Coast. The variety of coasters is incredible, from classic wooden coasters to modern steel giants. The park is well-maintained and the staff is friendly.",
      "author": {
        "id": 8,
        "username": "theme_park_lover",
        "display_name": "Theme Park Lover"
      },
      "helpful_count": 42,
      "is_verified": true,
      "visit_date": "2024-01-10",
      "created_at": "2024-01-11T16:00:00Z"
    }
  ]
}

Review Statistics

// Get review statistics
const response = await fetch('/api/v1/reviews/stats/');

// Response (200)
{
  "total_reviews": 25000,
  "park_reviews": 8000,
  "ride_reviews": 17000,
  "average_rating": 4.2,
  "reviews_by_rating": {
    "1": 500,
    "2": 1200,
    "3": 3800,
    "4": 8500,
    "5": 11000
  },
  "reviews_this_month": 1250,
  "verified_reviews": 15000,
  "top_reviewed_parks": [
    {
      "id": 1,
      "name": "Cedar Point",
      "review_count": 850,
      "average_rating": 4.6
    },
    {
      "id": 5,
      "name": "Magic Kingdom",
      "review_count": 720,
      "average_rating": 4.8
    }
  ],
  "top_reviewed_rides": [
    {
      "id": 1,
      "name": "Steel Vengeance",
      "review_count": 450,
      "average_rating": 4.9
    },
    {
      "id": 15,
      "name": "Millennium Force",
      "review_count": 380,
      "average_rating": 4.7
    }
  ]
}

Moderation API

Base Endpoints

GET /api/v1/moderation/queue/
POST /api/v1/moderation/queue/
GET /api/v1/moderation/queue/{id}/
PATCH /api/v1/moderation/queue/{id}/
DELETE /api/v1/moderation/queue/{id}/
GET /api/v1/moderation/reports/
POST /api/v1/moderation/reports/
GET /api/v1/moderation/reports/{id}/
PATCH /api/v1/moderation/reports/{id}/
GET /api/v1/moderation/stats/
GET /api/v1/moderation/actions/
POST /api/v1/moderation/actions/

Moderation Queue

// Get items in moderation queue (staff only)
const response = await fetch('/api/v1/moderation/queue/', {
  headers: {
    'Authorization': 'Bearer your_staff_token_here'
  }
});

// Response (200)
{
  "count": 25,
  "next": "http://localhost:8000/api/v1/moderation/queue/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "content_type": "photo",
      "content_object": {
        "id": 123,
        "image_url": "https://imagedelivery.net/account-hash/abc123def456/public",
        "caption": "Amazing ride photo",
        "uploaded_by": {
          "id": 5,
          "username": "photographer"
        }
      },
      "status": "PENDING",
      "priority": "NORMAL",
      "submitted_at": "2024-01-28T14:00:00Z",
      "submitted_by": {
        "id": 5,
        "username": "photographer",
        "display_name": "Photographer"
      },
      "flags": [
        {
          "type": "INAPPROPRIATE_CONTENT",
          "reason": "Photo may contain inappropriate content",
          "reporter": {
            "id": 8,
            "username": "concerned_user"
          }
        }
      ],
      "auto_flagged": false,
      "requires_review": true
    },
    {
      "id": 2,
      "content_type": "review",
      "content_object": {
        "id": 25,
        "title": "Terrible experience",
        "content": "This ride was awful and the staff was rude...",
        "rating": 1,
        "author": {
          "id": 12,
          "username": "angry_visitor"
        }
      },
      "status": "PENDING",
      "priority": "HIGH",
      "submitted_at": "2024-01-28T13:30:00Z",
      "submitted_by": {
        "id": 12,
        "username": "angry_visitor",
        "display_name": "Angry Visitor"
      },
      "flags": [
        {
          "type": "SPAM",
          "reason": "Multiple similar reviews from same user",
          "reporter": null
        }
      ],
      "auto_flagged": true,
      "requires_review": true
    }
  ]
}

Moderate Content

// Approve or reject content in moderation queue
const response = await fetch('/api/v1/moderation/queue/1/', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_staff_token_here'
  },
  body: JSON.stringify({
    status: "APPROVED",
    moderator_notes: "Content reviewed and approved - no issues found",
    action_taken: "APPROVE"
  })
});

// Success response (200)
{
  "id": 1,
  "content_type": "photo",
  "status": "APPROVED",
  "priority": "NORMAL",
  "moderator": {
    "id": 2,
    "username": "moderator_user",
    "display_name": "Moderator User"
  },
  "moderator_notes": "Content reviewed and approved - no issues found",
  "action_taken": "APPROVE",
  "reviewed_at": "2024-01-28T15:30:00Z",
  "submitted_at": "2024-01-28T14:00:00Z"
}

// Reject content
const rejectResponse = await fetch('/api/v1/moderation/queue/2/', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_staff_token_here'
  },
  body: JSON.stringify({
    status: "REJECTED",
    moderator_notes: "Content violates community guidelines - inappropriate language",
    action_taken: "REJECT",
    violation_type: "INAPPROPRIATE_LANGUAGE"
  })
});

// Rejection response (200)
{
  "id": 2,
  "content_type": "review",
  "status": "REJECTED",
  "priority": "HIGH",
  "moderator": {
    "id": 2,
    "username": "moderator_user",
    "display_name": "Moderator User"
  },
  "moderator_notes": "Content violates community guidelines - inappropriate language",
  "action_taken": "REJECT",
  "violation_type": "INAPPROPRIATE_LANGUAGE",
  "reviewed_at": "2024-01-28T15:35:00Z",
  "submitted_at": "2024-01-28T13:30:00Z"
}

Report Content

// Report inappropriate content
const response = await fetch('/api/v1/moderation/reports/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token_here'
  },
  body: JSON.stringify({
    content_type: "review",
    content_id: 25,
    report_type: "INAPPROPRIATE_CONTENT",
    reason: "Review contains offensive language and personal attacks",
    additional_details: "The review uses profanity and makes personal attacks against park staff"
  })
});

// Success response (201)
{
  "id": 15,
  "content_type": "review",
  "content_id": 25,
  "report_type": "INAPPROPRIATE_CONTENT",
  "reason": "Review contains offensive language and personal attacks",
  "additional_details": "The review uses profanity and makes personal attacks against park staff",
  "reporter": {
    "id": 1,
    "username": "current_user",
    "display_name": "Current User"
  },
  "status": "OPEN",
  "created_at": "2024-01-28T15:30:00Z",
  "updated_at": "2024-01-28T15:30:00Z"
}

List Reports

// Get reports (staff only)
const response = await fetch('/api/v1/moderation/reports/?status=OPEN', {
  headers: {
    'Authorization': 'Bearer your_staff_token_here'
  }
});

// Response (200)
{
  "count": 12,
  "results": [
    {
      "id": 15,
      "content_type": "review",
      "content_id": 25,
      "content_object": {
        "id": 25,
        "title": "Terrible experience",
        "content": "This ride was awful...",
        "author": {
          "id": 12,
          "username": "angry_visitor"
        }
      },
      "report_type": "INAPPROPRIATE_CONTENT",
      "reason": "Review contains offensive language and personal attacks",
      "reporter": {
        "id": 1,
        "username": "concerned_user",
        "display_name": "Concerned User"
      },
      "status": "OPEN",
      "priority": "HIGH",
      "created_at": "2024-01-28T15:30:00Z",
      "assigned_moderator": null
    }
  ]
}

Moderation Statistics

// Get moderation statistics (staff only)
const response = await fetch('/api/v1/moderation/stats/', {
  headers: {
    'Authorization': 'Bearer your_staff_token_here'
  }
});

// Response (200)
{
  "queue_stats": {
    "total_pending": 25,
    "total_approved_today": 45,
    "total_rejected_today": 8,
    "average_review_time_minutes": 15,
    "by_content_type": {
      "photo": 12,
      "review": 8,
      "park": 3,
      "ride": 2
    },
    "by_priority": {
      "LOW": 5,
      "NORMAL": 15,
      "HIGH": 4,
      "URGENT": 1
    }
  },
  "report_stats": {
    "total_open_reports": 12,
    "total_resolved_today": 18,
    "by_report_type": {
      "INAPPROPRIATE_CONTENT": 8,
      "SPAM": 3,
      "COPYRIGHT": 1,
      "MISINFORMATION": 0
    },
    "by_status": {
      "OPEN": 12,
      "IN_PROGRESS": 3,
      "RESOLVED": 150,
      "DISMISSED": 25
    }
  },
  "moderator_performance": [
    {
      "moderator": {
        "id": 2,
        "username": "moderator_user",
        "display_name": "Moderator User"
      },
      "reviews_today": 25,
      "average_review_time_minutes": 12,
      "approval_rate": 0.85
    }
  ],
  "auto_moderation": {
    "total_auto_flagged_today": 15,
    "accuracy_rate": 0.92,
    "false_positive_rate": 0.08
  }
}

Moderation Actions

// Get moderation action history
const response = await fetch('/api/v1/moderation/actions/?days=7', {
  headers: {
    'Authorization': 'Bearer your_staff_token_here'
  }
});

// Response (200)
{
  "count": 150,
  "results": [
    {
      "id": 1,
      "action_type": "APPROVE",
      "content_type": "photo",
      "content_id": 123,
      "moderator": {
        "id": 2,
        "username": "moderator_user",
        "display_name": "Moderator User"
      },
      "reason": "Content reviewed and approved - no issues found",
      "created_at": "2024-01-28T15:30:00Z"
    },
    {
      "id": 2,
      "action_type": "REJECT",
      "content_type": "review",
      "content_id": 25,
      "moderator": {
        "id": 2,
        "username": "moderator_user",
        "display_name": "Moderator User"
      },
      "reason": "Content violates community guidelines - inappropriate language",
      "violation_type": "INAPPROPRIATE_LANGUAGE",
      "created_at": "2024-01-28T15:35:00Z"
    }
  ]
}

Bulk Moderation Actions

// Perform bulk moderation actions
const response = await fetch('/api/v1/moderation/actions/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_staff_token_here'
  },
  body: JSON.stringify({
    action_type: "APPROVE",
    queue_item_ids: [1, 3, 5, 7],
    moderator_notes: "Bulk approval of reviewed content"
  })
});

// Success response (201)
{
  "message": "Bulk action completed successfully",
  "processed_items": 4,
  "successful_actions": 4,
  "failed_actions": 0,
  "results": [
    {
      "queue_item_id": 1,
      "status": "SUCCESS",
      "action": "APPROVED"
    },
    {
      "queue_item_id": 3,
      "status": "SUCCESS",
      "action": "APPROVED"
    },
    {
      "queue_item_id": 5,
      "status": "SUCCESS",
      "action": "APPROVED"
    },
    {
      "queue_item_id": 7,
      "status": "SUCCESS",
      "action": "APPROVED"
    }
  ]
}

Report Types

Available report types for content:

  • INAPPROPRIATE_CONTENT - Content contains inappropriate material
  • SPAM - Content is spam or promotional
  • COPYRIGHT - Content violates copyright
  • MISINFORMATION - Content contains false information
  • HARASSMENT - Content constitutes harassment
  • DUPLICATE - Content is a duplicate
  • OFF_TOPIC - Content is off-topic or irrelevant
  • OTHER - Other issues not covered above

Moderation Status Types

  • PENDING - Awaiting moderation review
  • APPROVED - Content approved and published
  • REJECTED - Content rejected and hidden
  • FLAGGED - Content flagged for additional review
  • ESCALATED - Content escalated to senior moderators

Priority Levels

  • LOW - Low priority, can be reviewed when convenient
  • NORMAL - Normal priority, standard review queue
  • HIGH - High priority, needs prompt attention
  • URGENT - Urgent priority, immediate attention required

Error Handling

  • 200 OK - Request successful
  • 201 Created - Resource created successfully
  • 202 Accepted - Request accepted for processing
  • 204 No Content - Request successful, no content to return
  • 400 Bad Request - Invalid request parameters
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource not found
  • 409 Conflict - Resource conflict (e.g., duplicate slug)
  • 422 Unprocessable Entity - Validation errors
  • 429 Too Many Requests - Rate limit exceeded
  • 500 Internal Server Error - Server error

Error Response Format

// Validation error (422)
{
  "detail": "Validation failed",
  "errors": {
    "name": ["This field is required."],
    "opening_date": ["Date cannot be in the future."],
    "min_height_in": ["Ensure this value is greater than or equal to 30."]
  }
}

// Authentication error (401)
{
  "detail": "Authentication credentials were not provided."
}

// Permission error (403)
{
  "detail": "You do not have permission to perform this action."
}

// Not found error (404)
{
  "detail": "Not found."
}

// Rate limit error (429)
{
  "detail": "Request was throttled. Expected available in 60 seconds.",
  "available_in": 60,
  "throttle_type": "user"
}

// Server error (500)
{
  "detail": "Internal server error",
  "error_id": "error_abc123def456"
}

Best Practices for Error Handling

async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    });

    // Handle different status codes
    if (response.status === 429) {
      const error = await response.json();
      throw new RateLimitError(error.detail, error.available_in);
    }

    if (response.status === 422) {
      const error = await response.json();
      throw new ValidationError(error.detail, error.errors);
    }

    if (response.status === 401) {
      // Redirect to login or refresh token
      throw new AuthenticationError('Authentication required');
    }

    if (response.status === 403) {
      throw new PermissionError('Insufficient permissions');
    }

    if (response.status === 404) {
      throw new NotFoundError('Resource not found');
    }

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new APIError(error.detail || 'Request failed', response.status);
    }

    return await response.json();
  } catch (error) {
    if (error instanceof TypeError) {
      throw new NetworkError('Network request failed');
    }
    throw error;
  }
}

// Custom error classes
class APIError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'APIError';
    this.status = status;
  }
}

class ValidationError extends APIError {
  constructor(message, errors) {
    super(message, 422);
    this.name = 'ValidationError';
    this.errors = errors;
  }
}

class RateLimitError extends APIError {
  constructor(message, retryAfter) {
    super(message, 429);
    this.name = 'RateLimitError';
    this.retryAfter = retryAfter;
  }
}

Performance Considerations

Caching Strategy

  • Stats API: Cached for 1 hour, auto-invalidated on data changes
  • Maps API: Cached for 5 minutes for location queries
  • Filter Options: Cached for 30 minutes
  • Photo URLs: Cloudflare Images provides automatic caching and CDN

Pagination Best Practices

// Use appropriate page sizes
const defaultPageSize = 20;
const maxPageSize = 1000;

// Implement infinite scroll
class InfiniteScroll {
  constructor(apiEndpoint, pageSize = 20) {
    this.apiEndpoint = apiEndpoint;
    this.pageSize = pageSize;
    this.currentPage = 1;
    this.hasMore = true;
    this.loading = false;
  }

  async loadMore() {
    if (this.loading || !this.hasMore) return;
    
    this.loading = true;
    try {
      const response = await fetch(
        `${this.apiEndpoint}?page=${this.currentPage}&page_size=${this.pageSize}`
      );
      const data = await response.json();
      
      this.currentPage++;
      this.hasMore = !!data.next;
      
      return data.results;
    } finally {
      this.loading = false;
    }
  }
}

Query Optimization

// Combine filters to reduce API calls
const optimizedQuery = {
  search: 'steel',
  category: 'RC',
  status: 'OPERATING',
  min_rating: 8.0,
  ordering: '-average_rating',
  page_size: 50 // Larger page size for fewer requests
};

// Use debouncing for search inputs
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const debouncedSearch = debounce(async (query) => {
  const results = await apiRequest(`/api/v1/rides/?search=${encodeURIComponent(query)}`);
  updateSearchResults(results);
}, 300);

Image Optimization

// Use appropriate image variants
const getImageUrl = (photo, size = 'medium') => {
  if (!photo || !photo.image_variants) return null;
  
  // Choose appropriate size based on use case
  const sizeMap = {
    thumbnail: photo.image_variants.thumbnail, // 150x150
    medium: photo.image_variants.medium,       // 500x500
    large: photo.image_variants.large,         // 1000x1000
    public: photo.image_variants.public        // Original size
  };
  
  return sizeMap[size] || photo.image_url;
};

// Lazy loading implementation
const lazyLoadImages = () => {
  const images = document.querySelectorAll('img[data-src]');
  const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.classList.remove('lazy');
        observer.unobserve(img);
      }
    });
  });

  images.forEach(img => imageObserver.observe(img));
};

Rate Limiting

// Implement client-side rate limiting
class RateLimiter {
  constructor(maxRequests = 100, windowMs = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = [];
  }

  canMakeRequest() {
    const now = Date.now();
    this.requests = this.requests.filter(time => now - time < this.windowMs);
    return this.requests.length < this.maxRequests;
  }

  recordRequest() {
    this.requests.push(Date.now());
  }
}

const rateLimiter = new RateLimiter();

async function rateLimitedRequest(url, options) {
  if (!rateLimiter.canMakeRequest()) {
    throw new Error('Rate limit exceeded');
  }
  
  rateLimiter.recordRequest();
  return await apiRequest(url, options);
}

Frontend Integration Examples

React Hook for API Integration

import { useState, useEffect, useCallback } from 'react';

export function useAPI(endpoint, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchData = useCallback(async () => {
    setLoading(true);
    setError(null);
    
    try {
      const response = await apiRequest(endpoint, options);
      setData(response);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [endpoint, JSON.stringify(options)]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch: fetchData };
}

// Usage
function RidesList() {
  const { data, loading, error } = useAPI('/api/v1/rides/', {
    method: 'GET',
    headers: { Authorization: 'Bearer token' }
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      {data?.results?.map(ride => (
        <div key={ride.id}>{ride.name}</div>
      ))}
    </div>
  );
}

Vue.js Composable

import { ref, reactive, computed } from 'vue';

export function useAPI(endpoint, options = {}) {
  const data = ref(null);
  const loading = ref(false);
  const error = ref(null);

  const fetchData = async () => {
    loading.value = true;
    error.value = null;
    
    try {
      const response = await apiRequest(endpoint, options);
      data.value = response;
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  };

  return {
    data: computed(() => data.value),
    loading: computed(() => loading.value),
    error: computed(() => error.value),
    fetchData
  };
}

This comprehensive documentation covers ALL available API endpoints, responses, and integration patterns for the ThrillWiki frontend. It includes detailed examples for every endpoint, error handling strategies, performance optimization techniques, and frontend framework integration examples.