ruvnet/RuView · warning · HTTPException

End time must be after start time

Error message

End time must be after start time

What it means

HTTP 400 from POST /pose/historical when end_time <= start_time in HistoricalDataRequest. The comparison uses <=, so equal timestamps are rejected too — the range must be strictly positive. The check runs on parsed datetimes inside the handler, before the 7-day cap and before the query.

Source

Thrown at archive/v1/src/api/routers/pose.py:249

    except Exception as e:
        logger.error(f"Error getting zones summary: {e}")
        raise HTTPException(
            status_code=500,
            detail="An internal error occurred. Please try again later."
        )


@router.post("/historical")
async def get_historical_data(
    request: HistoricalDataRequest,
    pose_service: PoseService = Depends(get_pose_service),
    current_user: Dict = Depends(require_auth)
):
    """Get historical pose estimation data."""
    try:
        # Validate time range
        if request.end_time <= request.start_time:
            raise HTTPException(
                status_code=400,
                detail="End time must be after start time"
            )
        
        # Limit query range to prevent excessive data
        max_range = timedelta(days=7)
        if request.end_time - request.start_time > max_range:
            raise HTTPException(
                status_code=400,
                detail="Query range cannot exceed 7 days"
            )
        
        data = await pose_service.get_historical_data(
            start_time=request.start_time,
            end_time=request.end_time,
            zone_ids=request.zone_ids,
            aggregation_interval=request.aggregation_interval,
            include_raw_data=request.include_raw_data

View on GitHub (pinned to 4685618388)

Solutions

  1. Fix or swap the fields so end_time > start_time
  2. If equal timestamps occur naturally (same-instant presets), nudge end_time by one interval (e.g. +1 second)
  3. Validate the range client-side before sending (see exampleFix)

Example fix

# before
body = {'start_time': iso_to, 'end_time': iso_from}  # swapped

# after
assert iso_from < iso_to, 'end_time must be after start_time'
body = {'start_time': iso_from, 'end_time': iso_to}
Defensive patterns

Strategy: validation

Validate before calling

from datetime import timedelta

def valid_range(start, end):
    if end <= start:
        raise ValueError('end_time must be after start_time')
    if end - start > timedelta(days=7):
        raise ValueError('range must be <= 7 days')
    return start, end

Type guard

def is_valid_time_range(start, end) -> bool:
    return start is not None and end is not None and end > start

Try / catch

resp = await client.post('/pose/historical', json=body)
if resp.status_code == 400 and 'after start time' in resp.json().get('detail', ''):
    body['start_time'], body['end_time'] = min(body['start_time'], body['end_time']), max(body['start_time'], body['end_time'])
    resp = await client.post('/pose/historical', json=body)
resp.raise_for_status()

Prevention

When it happens

Trigger: POST /pose/historical with end_time equal to or earlier than start_time — e.g. a 'today' preset that produces start == end at midnight, or from/to fields wired in reverse.

Common situations: Date-range pickers returning the same boundary instant for both ends; from/to fields swapped in the client; timezone conversion collapsing both values to the same instant.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/df681be044caa076. Report an issue: GitHub.