# ShiftOps API Documentation


**Last Updated:** 2025-11-20

## Endpoints

### POST `/v2/api/shiftops.php`

Main analysis endpoint that processes business data and returns ShiftOps scores.

**File:** `v2/api/shiftops.php` (6084 lines)  
**Class:** `ShiftOpsAnalyzer` (lines 708-6084)

#### Request

**Headers:**

```
Content-Type: application/json
```

**Body:**

```json
{
    "mode": "essential" | "enhanced",
    "place_id": "string (required)",
    "name": "string (required)",
    "formatted_address": "string",
    "website": "string",
    "types": ["array", "of", "strings"],
    "rating": 4.5,
    "photos": ["array", "of", "photo", "objects"],
    "geometry": {
        "location": {
            "lat": 52.5200,
            "lng": 13.4050
        }
    },
    "user_ratings_total": 150,
    "opening_hours": {
        "periods": [...]
    },
    "business_status": "OPERATIONAL",
    "price_level": 2,
    "wheelchair_accessible_entrance": true,
    "service_options": {
        "delivery": true,
        "dine_in": true,
        "takeout": true,
        "reservable": false
    },
    "reviews": ["array", "of", "review", "objects"],
    "utc_offset": 60,
    "address_components": ["array", "of", "components"]
}
```

**Required Fields:**

- `place_id`
- `name`

**Optional Fields:**

- All other fields are optional but improve accuracy

#### Response

**Success (200):**

```json
{
    "success": true,
    "analysis": {
        "business_info": {...},
        "location_analysis": {...},
        "online_presence": {...},
        "shiftops_score": {
            "total_score": 65,
            "pillar_scores": {
                "scheduling_efficiency": 14,
                "absence_stability": 13,
                "time_tracking_hygiene": 12,
                "compliance_docs": 13,
                "payroll_readiness": 13
            },
            "grade": "C",
            "benchmark_comparison": {...},
            "data_completeness": {...}
        },
        "cost_savings": {...},
        "context_data": {...},
        "is_ordio_customer": false,
        "customer_match_confidence": 0
    },
    "mode": "essential",
    "timestamp": "2025-01-15 10:30:00",
    "business_name": "Example Restaurant"
}
```

**Error (400):**

```json
{
  "success": false,
  "error": "Place ID and business name are required"
}
```

**Error (500):**

```json
{
  "success": false,
  "error": "Internal server error",
  "debug": "Error message"
}
```

#### Modes

**Essential Mode:**

- Fast response (< 2 seconds)
- Minimal data for loading screen
- Includes: basic scores, cost savings, essential context

**Enhanced Mode:**

- Slower response (3-5 seconds)
- Complete analysis
- Includes: website analysis, competitor analysis, full recommendations, detailed weather forecast

#### Caching

- Cache key: MD5 hash of place_id, name, rating, user_ratings_total, types, mode
- Format: `shiftops_analysis_{md5_hash}.json`
- TTL: 1 hour (3600 seconds)
- Location: `v2/cache/systems/shiftops/{cache_key}.json`
- Cache hit returns same data structure
- Automatic expiration on read (checks file timestamp)
- Manual cleanup on cache miss (deletes old files)

#### Request ID Tracking

Every request generates a unique `request_id` for logging traceability:

- Format: `shiftops_{uniqid}`
- Used in all error logs for tracking
- Returned in response: `"request_id": "shiftops_6914e366c1f3b3.43282067"`

#### Error Handling

The endpoint includes comprehensive error handling:

- Fatal error handler returns valid JSON with fallback data
- Each analysis step wrapped in try-catch
- Request ID included in all error logs
- Fallback data structure ensures response always succeeds

---

### POST `/v2/api/shiftops-hubspot.php`

Submits lead data to HubSpot and logs rich ShiftOps data.

#### Request

**Headers:**

```
Content-Type: application/json
```

**Body:**

```json
{
  "email": "user@example.com (required)",
  "firstname": "John",
  "lastname": "Doe",
  "shiftops_business_name": "Example Restaurant",
  "shiftops_business_address": "123 Main St",
  "shiftops_business_type": "restaurant",
  "shiftops_index": 65,
  "shiftops_grade": "C",
  "shiftops_scheduling_score": 14,
  "shiftops_absence_score": 13,
  "shiftops_timetracking_score": 12,
  "shiftops_compliance_score": 13,
  "shiftops_payroll_score": 13,
  "shiftops_place_id": "ChIJ...",
  "website": "https://example.com",
  "shiftops_google_rating": 4.5,
  "utm_source": "google",
  "utm_medium": "cpc",
  "utm_campaign": "shiftops_launch",
  "utm_term": "",
  "utm_content": "",
  "gclid": "",
  "page_url": "https://ordio.de/v2/pages/shiftops-report.php",
  "referrer": "https://google.com"
}
```

**Required Fields:**

- `email` (must be valid email address)

**Optional Fields:**

- All ShiftOps data fields
- UTM parameters
- Tracking data

#### Response

**Success (200):**

```json
{
    "success": true,
    "message": "Lead submitted successfully to HubSpot",
    "lead_data": {
        "email": "user@example.com",
        "firstname": "John",
        "lastname": "Doe",
        "business": "Example Restaurant",
        "shiftops_index": 65,
        "grade": "C"
    },
    "hubspot_response": {...}
}
```

**Error (400):**

```json
{
  "success": false,
  "error": "Valid email is required"
}
```

**Error (500):**

```json
{
  "success": false,
  "error": "Connection error: ...",
  "details": "Could not connect to HubSpot API",
  "log_location": "/logs/shiftops-hubspot.log"
}
```

#### Data Logging

- Rich data logged to: `logs/shiftops-leads-full.log` (JSON format, one entry per line)
- Submission log: `logs/shiftops-hubspot.log`
- Includes all ShiftOps data, UTM parameters, and tracking information

#### HubSpot Configuration

- Portal ID: `145133546`
- Form ID: `41d07332-6697-4daa-b27e-dd60515f9c0f`
- Rate limiting: 0.6s delay between requests (100 requests per 10 seconds)

---

### GET `/v2/api/shiftops-health-check.php`

Diagnostic endpoint for customer matcher status and system health.

**File:** `v2/api/shiftops-health-check.php` (123 lines)

#### Request

**Method:** GET  
**No parameters required**

#### Response

**Success (200):**

```json
{
  "success": true,
  "timestamp": "2025-11-12 19:46:54",
  "checks": {
    "matcher_file": {
      "exists": true,
      "path": "/var/www/lexikon/v2/api/shiftops-customer-matcher.php",
      "readable": true
    },
    "customers_file": {
      "exists": true,
      "path": "/var/www/lexikon/v2/api/../data/ordio-customers.json",
      "readable": true,
      "size": 1415120,
      "size_mb": 1.35,
      "permissions": "0644"
    },
    "matcher_class": {
      "available": true
    },
    "matcher_initialization": {
      "success": true,
      "class": "ShiftOpsCustomerMatcher"
    },
    "customer_count": {
      "success": true,
      "count": 3018
    }
  },
  "php_config": {
    "memory_limit": "128M",
    "max_execution_time": "30",
    "current_memory_usage": "4.69 MB",
    "peak_memory_usage": "6.04 MB"
  },
  "message": "Health check passed: 3018 customers loaded"
}
```

**Use Cases:**

- Verify customer matcher is working
- Check customer file exists and is readable
- Get customer count
- Check PHP configuration
- Debug production issues

---

### GET `/v2/api/shiftops-test-matching.php`

Test endpoint for debugging customer matching with detailed output.

**File:** `v2/api/shiftops-test-matching.php` (172 lines)

#### Request

**Method:** GET

**Query Parameters:**

- `name` (string, required): Business name to test
- `website` (string, optional): Business website/domain
- `address` (string, optional): Business address

#### Response

**Success (200):**

```json
{
  "success": true,
  "test_data": {
    "business_name": "La Fonda",
    "normalized_name": "la fonda",
    "website": "https://lafonda.koeln",
    "extracted_domain": "lafonda.koeln"
  },
  "match_result": {
    "matched": true,
    "company_name": "La Fonda",
    "domain": "lafonda.koeln",
    "match_type": "domain",
    "confidence": 95,
    "matched_customer": {
      "name": "La Fonda",
      "domain": "lafonda.koeln"
    }
  },
  "matching_details": {
    "strategies_tried": ["domain", "exact", "prefix", "fuzzy"],
    "domain_match_attempted": true,
    "exact_match_attempted": true,
    "fuzzy_match_attempted": false,
    "character_comparison": "..."
  },
  "customer_count": 3018
}
```

**Use Cases:**

- Debug why a specific business doesn't match
- Test domain extraction logic
- Verify name normalization
- Character-by-character comparison for debugging

---

## Additional Endpoints

### POST `/v2/api/shiftops-hubspot-customers.php`

Syncs customer list from HubSpot and stores in JSON file.

**File:** `v2/api/shiftops-hubspot-customers.php` (547 lines)

**Note:** This is typically run as a cron job or manual sync, not called from frontend.

**Output:** Updates `v2/data/ordio-customers.json` with latest customer data from HubSpot.

---

## Data Structures

**Note:** For complete data structure documentation, see [SHIFTOPS_DATA_STRUCTURES.md](SHIFTOPS_DATA_STRUCTURES.md).

### ShiftOps Score

```typescript
{
    total_score: number; // 0-100
    pillar_scores: {
        scheduling_efficiency: number; // 0-20
        absence_stability: number; // 0-20
        time_tracking_hygiene: number; // 0-20
        compliance_docs: number; // 0-20
        payroll_readiness: number; // 0-20
    };
    grade: "A+" | "A" | "A-" | "B+" | "B" | "B-" | "C+" | "C" | "C-" | "D+" | "D" | "D-" | "F";
    benchmark_comparison: {
        percentile: number; // 0-100
        industry_average: number;
        difference: number;
    };
    data_completeness: {
        percentage: number; // 0-100
        multiplier: number; // 0.50-1.0
        confidence: "high" | "medium" | "low";
        impact: "low" | "moderate" | "high";
    };
    score_metadata: {
        data_sources: string[];
        confidence_level: string;
        last_updated: string;
        is_ordio_customer: boolean;
        customer_boost_applied: boolean;
    };
}
```

### Cost Savings

```typescript
{
    team_size_estimate: number;
    team_size_confidence: "low" | "medium" | "high";
    monthly_labor_cost: number;
    annual_labor_cost: number;
    total_monthly_savings: number;
    total_annual_savings: number;
    savings_percentage: number;
    potential_savings: {
        overtime_reduction: {
            current_overtime_cost: number;
            potential_savings: number;
            savings_percentage: number;
        };
        absence_cost_reduction: {...};
        compliance_avoidance: {...};
        turnover_reduction: {...};
        scheduling_optimization: {...};
    };
    roi_analysis: {
        ordio_monthly_cost: number; // 89
        potential_monthly_savings: number;
        net_monthly_benefit: number;
        roi_percentage: number;
        payback_period_months: number;
    };
    break_even_analysis: {
        break_even_team_size: number;
        break_even_monthly_savings: number;
        break_even_months: number;
        is_already_profitable: boolean;
    };
}
```

### Competitive Positioning

```typescript
{
    competitor_analysis: {
        competitor_count: number;
        average_rating: number;
        average_price_level: number;
        average_review_count: number;
        top_competitors: Array<{
            name: string;
            rating: number;
            user_ratings_total: number;
        }>;
        competition_density: "very_low" | "low" | "medium" | "high" | "very_high";
    };
    market_position: {
        overall_position: "market_leader" | "above_average" | "average" | "below_average";
        rating_percentile: number;
        review_percentile: number;
        price_position: "premium" | "average" | "budget";
        competitive_strength: string;
        market_share_estimate: number;
    };
    growth_potential: {
        growth_score: number;
        growth_potential: "very_high" | "high" | "medium" | "low" | "very_low";
        growth_factors: {...};
        key_opportunities: string[];
        growth_timeline: string;
    };
}
```

### Recommendations

```typescript
{
    quick_wins: Array<{
        pillar: string;
        category: "quick_wins";
        priority: "critical" | "high" | "medium" | "low";
        title: string;
        description: string;
        action: string;
        impact: "high" | "medium" | "low";
        effort: "high" | "medium" | "low";
        timeline: string;
        cost_savings?: number;
        ordio_feature: string;
        success_metrics: string[];
    }>;
    high_value: Array<{...}>;
    strategic: Array<{...}>;
    all_recommendations: Array<{...}>;
    implementation_roadmap: {
        phase_1: {
            name: string;
            recommendations: Array<{...}>;
        };
        phase_2: {...};
        phase_3: {...};
    };
}
```

## Error Codes

### HTTP Status Codes

- `200 OK`: Successful request
- `400 Bad Request`: Missing required fields or invalid input
- `405 Method Not Allowed`: Wrong HTTP method (must be POST)
- `500 Internal Server Error`: Server-side error

### Error Response Format

```json
{
  "success": false,
  "error": "Error message",
  "details": "Additional details (optional)",
  "debug": "Debug information (only in development)"
}
```

## Rate Limiting

### Google Places API

- Free tier: 1000 requests/day
- Caching helps reduce API calls

### HubSpot API

- 100 requests per 10 seconds
- Rate limit delay: 0.6s between requests

### Weather API (Open-Meteo)

- No rate limits (free tier)
- No API key required

### Holiday API (Nager.Date)

- No rate limits (free tier)
- No API key required

## Best Practices

1. **Use Essential Mode First**

   - Fast initial response
   - Show loading screen
   - Then fetch enhanced data in background

2. **Cache Results**

   - Results are cached for 1 hour
   - Reuse same place_id for faster responses

3. **Handle Errors Gracefully**

   - Check for `success: false`
   - Display user-friendly error messages
   - Log errors for debugging

4. **Validate Input**

   - Always include `place_id` and `name`
   - Validate email format for HubSpot submission
   - Sanitize user input

5. **Monitor Performance**
   - Essential mode: < 2 seconds
   - Enhanced mode: 3-5 seconds
   - Cache hits: < 100ms

## Testing

### Test Essential Mode

```bash
curl -X POST http://localhost:8003/v2/api/shiftops.php \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "essential",
    "place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "name": "Test Restaurant"
  }'
```

### Test Enhanced Mode

```bash
curl -X POST http://localhost:8003/v2/api/shiftops.php \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "enhanced",
    "place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "name": "Test Restaurant"
  }'
```

### Test HubSpot Submission

```bash
curl -X POST http://localhost:8003/v2/api/shiftops-hubspot.php \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "firstname": "Test",
    "lastname": "User"
  }'
```

---

### POST `/v2/api/shiftops-nps.php`

NPS survey submission endpoint for collecting user feedback on ShiftOps tool.

**File:** `v2/api/shiftops-nps.php` (238 lines)

#### Request

**Headers:**

```
Content-Type: application/json
```

**Body:**

```json
{
  "email": "string (required, valid email)",
  "nps_score": 0-10 (required, integer),
  "nps_feedback": "string (optional)",
  "shiftops_business_name": "string (optional)",
  "shiftops_index": 0-100 (optional, integer),
  "shiftops_grade": "string (optional, e.g., 'A', 'B', 'C')",
  "shiftops_place_id": "string (optional, Google Places place_id)",
  "page_url": "string (optional)",
  "user_agent": "string (optional)",
  "nps_survey_version": "string (optional, default: 'beta-v1')"
}
```

**Required Fields:**

- `email` - Valid email address
- `nps_score` - Integer between 0-10

**Optional Fields:**

- `nps_feedback` - Free text feedback
- `shiftops_business_name` - Business name from ShiftOps analysis
- `shiftops_index` - ShiftOps score (0-100)
- `shiftops_grade` - ShiftOps grade (A+, A, A-, B+, B, B-, C+, C, C-, D+, D, D-, F)
- `shiftops_place_id` - Google Places place_id
- `page_url` - Page URL where survey was submitted
- `user_agent` - User agent string
- `nps_survey_version` - Survey version identifier

#### Response

**Success (200):**

```json
{
  "success": true,
  "message": "NPS feedback submitted successfully to HubSpot",
  "nps_data": {
    "email": "user@example.com",
    "nps_score": 9,
    "nps_category": "Promoter",
    "business": "Example Restaurant",
    "shiftops_index": 75,
    "grade": "B"
  },
  "hubspot_response": {...}
}
```

**Error (400):**

```json
{
  "success": false,
  "error": "Valid NPS score (0-10) is required"
}
```

**Error (500):**

```json
{
  "success": false,
  "error": "Connection error: ...",
  "details": "Could not connect to HubSpot API",
  "log_location": "/logs/shiftops-nps.log"
}
```

#### NPS Category Calculation

- **Promoter:** NPS score 9-10
- **Passive:** NPS score 7-8
- **Detractor:** NPS score 0-6

#### Data Logging

- Rich data logged to: `logs/shiftops-nps.log` (JSON format, one entry per line)
- Includes all NPS data, ShiftOps context, and HubSpot submission details

#### HubSpot Configuration

- Portal ID: `145133546`
- Form ID: `804459f7-c18d-4da6-8a0b-a81f44bb8275`
- Context data consolidated into `shiftops_nps_context` field

#### Test NPS Submission

```bash
curl -X POST http://localhost:8003/v2/api/shiftops-nps.php \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "nps_score": 9,
    "nps_feedback": "Great tool!",
    "shiftops_business_name": "Test Restaurant",
    "shiftops_index": 75,
    "shiftops_grade": "B"
  }'
```
