Data Analysis
The /analyze endpoint inspects a single time
series and returns a rich diagnostic profile — pattern type, trend, seasonality, intermittency, anomalies and
a data-quality score — without generating a forecast. Use it to understand your data, validate
it before forecasting, or surface insights in a dashboard.
Endpoint
/v2/analyze
Authentication
Bearer YOUR_API_KEY
Analyze Time Series
/analyze
Send your history exactly as you would to /forecast.
The response contains two independent analyses of the same series:
result.generic
A fast, lightweight profile computed in-process. Always present, even for very short or sparse series. Great for quick heuristics and UI badges.
result.detailed
An in-depth analysis from the statistical engine — STL/ACF seasonality, distribution stats, and model recommendations. Richer, but needs a bit more history.
Common uses
- Decide whether a series is forecastable (enough history, not too sparse) before spending a forecast call.
- Pick a model or
data_typebased on detected pattern, trend and seasonality. - Show data-quality and confidence indicators to end users.
- Flag anomalies, recent spikes and trend changes for review.
Request Body
{
"identifier": "SKU-12345",
"data": [
{"date": "2022-01-01", "value": 118},
{"date": "2022-02-01", "value": 126},
{"date": "2022-03-01", "value": 141}
],
"frequency": "M",
"data_type": "sales",
"periods": 6,
"enable_intelligent_aggregation": true
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| identifier | string | Yes | Unique label for the series (SKU, product ID, metric name). Echoed back as result.detailed.identifier. |
| data | array | Yes | Array of { "date", "value" } points. All dates must share one format — either YYYY-MM-DD or YYYY-MM-DD HH:MM:SS — and every value must be numeric. Subject to the same per-plan data-point limits as /forecast. |
| frequency | string | Yes | The spacing of your data: H, D, W, M, MS, ME, Q, or Y. |
| data_type | string | Yes | The kind of series — e.g. sales, demand, inventory, web_traffic, revenue, cpu_usage, api_requests, or any custom string. Drives the data_characteristics block and model recommendations. |
| periods | integer | Yes | Forecast horizon (≥ 1). Required by the shared request schema; accepted but does not change the analysis output — this endpoint never produces a forecast. Pass the horizon you intend to forecast with later. |
| start_date | string | No | Optional anchor date (YYYY-MM-DD) used when aligning your points to the frequency grid. |
| tenant_context | string | No | Optional tag for multi-tenant scoping. Passed through untouched. |
| enable_intelligent_aggregation | boolean | No | Default true. Rolls finer-grained points up to frequency before analysis. Affects total_periods vs original_periods and the data_info reduction metrics, and relaxes the irregular-interval validation check. |
The endpoint also accepts the shared forecasting options (model,
selection_metric,
confidence_level and the
enable_* tuning flags) for schema compatibility, but they
have no effect here — analysis always runs on the standard statistical engine.
Response
{
"result": {
"generic": {
"pattern_type": "regular",
"characteristics": {
"total_periods": 24,
"original_periods": 24,
"non_zero_events": 24,
"non_zero_ratio": 1,
"mean_value": 142.6,
"std_value": 21.4,
"coefficient_of_variation": 0.15,
"min_value": 105,
"max_value": 188,
"avg_interval": 1,
"cv_non_zero_values": 0.15,
"mean_non_zero_value": 142.6,
"recent_pattern_change": false
},
"anomalies": {
"anomaly_count": 0,
"anomaly_ratio": 0,
"has_recent_spike": false,
"recent_spike_ratio": 1.04,
"max_spike_ratio": 1.32,
"upper_bound": 205.5,
"lower_bound": 79.5
},
"trend": {
"correlation": 0.72,
"has_trend": true,
"changepoint_count": 1,
"has_recent_change": false
},
"seasonality": {
"potential_seasonality": true,
"detected_periods": [12],
"has_seasonality": true
},
"data_quality": {
"completeness_score": 1,
"reliability_score": 0.95,
"issues_detected": [],
"forecast_confidence": "high",
"confidence_by_horizon": {
"short_term": 1,
"medium_term": 0.95,
"long_term": 0.75
}
},
"insights": {
"volatility_level": "low",
"predictability": "high",
"data_maturity": "sufficient",
"seasonal_strength": 0.34,
"recommended_horizon": 12
},
"warnings": []
},
"detailed": {
"identifier": "SKU-12345",
"analysis": {
"data_validation": {
"valid": true,
"issues": [],
"quality_score": 0.95,
"quality_factors": {
"data_sufficiency": 0.8,
"completeness": 1,
"variance": 1,
"non_negative": 1
},
"data_points": 24,
"date_range_days": 700
},
"basic_statistics": {
"count": 24,
"mean": 142.6,
"median": 140.5,
"std": 21.4,
"min": 105,
"max": 188,
"cv": 0.15,
"skewness": 0.21,
"kurtosis": -0.44,
"q25": 126.75,
"q75": 158.25,
"iqr": 31.5
},
"intermittency": {
"zero_ratio": 0,
"cv2": 0.023,
"adi": 1,
"avg_interval": 1,
"is_intermittent": false,
"demand_pattern": "smooth"
},
"seasonality": {
"has_seasonality": true,
"seasonal_strength": 0.34,
"trend_strength": 0.61,
"seasonal_periods": [12],
"primary_period": 12,
"stl_analysis": { "note": "raw STL decomposition output" },
"acf_pacf_analysis": { "note": "raw ACF / PACF output" },
"recommendation": {
"model_suggestions": ["AutoETS", "AutoARIMA", "SeasonalNaive"],
"use_seasonal_models": true,
"suggested_periods": 12
}
},
"trend_analysis": {
"has_trend": true,
"trend_direction": "increasing",
"trend_strength": 0.72,
"trend_significance": 0.72,
"slope": 1.83,
"trend_consistency": 0.68,
"correlation_with_time": 0.72
},
"pattern_classification": {
"pattern_type": "seasonal_trending",
"pattern_characteristics": ["increasing trend", "seasonal"],
"complexity_level": "medium",
"complexity_score": 2,
"forecasting_difficulty": "easy",
"is_intermittent": false,
"has_seasonality": true,
"has_trend": true,
"recommended_approach": {
"primary_methods": ["AutoETS", "AutoARIMA", "MSTL"],
"reasoning": "Complex seasonal trending patterns need advanced seasonal methods"
}
},
"data_characteristics": {
"intermittent_likely": true,
"seasonal_patterns": ["weekly", "monthly", "yearly"],
"trend_common": true,
"outliers_common": true,
"preferred_models": ["croston", "ses", "ets"]
},
"external_features": {
"has_regressors": false,
"regressor_cols": [],
"regressor_count": 0
}
},
"data_info": {
"original_data_points": 24,
"aggregated_points": 24,
"aggregation_enabled": true,
"data_reduction_ratio": 0,
"date_range": { "start": "2022-01-01", "end": "2023-12-01" },
"value_stats": {
"min": 105,
"max": 188,
"mean": 142.6,
"std": 21.4,
"zero_count": 0,
"non_zero_count": 24
}
}
}
},
"meta": {
"timing": { "validation": 6.4, "analysing": 214.8, "total": 221.2 }
}
}
Two pattern taxonomies. result.generic.pattern_type
describes intermittency only (Syntetos–Boylan), while
result.detailed.analysis.pattern_classification.pattern_type
combines trend, seasonality and intermittency into a broader label. The two can legitimately differ for the same series.
Response Fields — result.generic
The lightweight profile. Every field is always present (values fall back sensibly for tiny or all-zero series).
Top level
| Field | Type | Description |
|---|---|---|
| pattern_type | string | Overall demand shape. One of regular, smooth_intermittent, erratic_intermittent, erratic_regular, no_values, or no_data. |
| characteristics | object | Core descriptive statistics (see below). |
| anomalies | object | Outlier and spike detection (see below). |
| trend | object | Trend direction and stability (see below). |
| seasonality | object | Detected seasonal periods (see below). |
| data_quality | object | Reliability scoring and forecast confidence (see below). |
| insights | object | Human-friendly summary indicators (see below). |
| warnings | array<string> | Human-readable caveats (recent spike, extreme values, recent trend change). Empty when none apply. |
generic.characteristics
| Field | Type | Description |
|---|---|---|
| total_periods | integer | Number of periods after aggregation — what a forecast would actually use. |
| original_periods | integer | Number of raw points you submitted. |
| non_zero_events | integer | Count of values greater than zero. |
| non_zero_ratio | float | Fraction of points with activity (0–1). Low values indicate sparse data. |
| mean_value / std_value | float | Mean and standard deviation of the raw values. |
| coefficient_of_variation | float | std ÷ mean — relative volatility. Higher means noisier data. |
| min_value / max_value | float | Smallest and largest observed values. |
| avg_interval | float | Average gap (in periods) between non-zero values. Large/infinite when the series is all zeros. |
| cv_non_zero_values | float | Coefficient of variation computed over only the non-zero values. |
| mean_non_zero_value | float | Average of the non-zero values only. |
| recent_pattern_change | boolean | True when the most recent window jumped sharply versus earlier history. |
generic.anomalies
| Field | Type | Description |
|---|---|---|
| anomaly_count | integer | Points falling outside the 1.5×IQR fences. |
| anomaly_ratio | float | Anomalies as a fraction of all points (0–1). |
| has_recent_spike | boolean | True when the last ~10% of data averages more than 2× or less than 0.5× the overall mean. |
| recent_spike_ratio | float | Recent-window mean ÷ overall mean. |
| max_spike_ratio | float | Largest value ÷ overall mean. |
| upper_bound / lower_bound | float | The IQR outlier fences used to flag anomalies. |
generic.trend
| Field | Type | Description |
|---|---|---|
| correlation | float | Correlation of value against time, −1 to 1. Positive means rising, negative means falling. |
| has_trend | boolean | True when |correlation| > 0.3. |
| changepoint_count | integer | Number of detected level shifts in the series. |
| has_recent_change | boolean | True when a changepoint falls in the last 30% of the series. |
generic.seasonality
| Field | Type | Description |
|---|---|---|
| potential_seasonality | boolean | True when there are at least 12 points and non-zero variance — enough to look for seasonality. |
| detected_periods | array<integer> | Lags whose autocorrelation exceeds 0.5 (checks 12, 4 and 7 — e.g. yearly, quarterly, weekly). |
| has_seasonality | boolean | True when the measured seasonal strength exceeds 0.15. |
generic.data_quality
| Field | Type | Description |
|---|---|---|
| completeness_score | float | How much of the series carries signal (equals non_zero_ratio), 0–1. |
| reliability_score | float | Overall trust score (0–1), penalized for volatility, anomalies and frequent changepoints. |
| issues_detected | array<string> | Any of sparse_data, high_anomaly_rate, recent_anomalies, unstable_trends, recent_trend_change. |
| forecast_confidence | string | Mapped from reliability_score: high, medium, low, or very_low. |
| confidence_by_horizon | object | Confidence (0–1) for short_term (1–3), medium_term (4–12) and long_term (12+) horizons. |
generic.insights
| Field | Type | Description |
|---|---|---|
| volatility_level | string | low (CV ≤ 0.5), medium (≤ 1), or high (> 1). |
| predictability | string | high, medium, or low, from volatility, anomaly rate and trend clarity. |
| data_maturity | string | limited (< 24 points) or sufficient (≥ 24). |
| seasonal_strength | float | Strength of the strongest detected seasonal cycle, 0–1. |
| recommended_horizon | integer | Suggested number of periods to forecast (1–24), scaled by reliability, pattern and history length. |
Response Fields — result.detailed
The in-depth analysis. It wraps the echoed identifier, an
analysis object with several sub-analyses, and a
data_info block describing what was actually analyzed. If the
statistical engine fails, detailed instead carries an
error object (see Errors).
detailed.analysis.data_validation
| Field | Type | Description |
|---|---|---|
| valid | boolean | True when no quality issues were found. |
| issues | array<string> | Detected problems, e.g. missing values, negative values, zero variance, too few points, or irregular intervals. |
| quality_score | float | Composite 0–1 score, the mean of the four quality_factors. |
| quality_factors | object | data_sufficiency (points ÷ 30, capped at 1), completeness, variance, and non_negative, each 0–1. |
| data_points | integer | Number of points analyzed. |
| date_range_days | integer | Calendar span between the first and last point, in days. |
detailed.analysis.basic_statistics
| Field | Type | Description |
|---|---|---|
| count | integer | Number of values. |
| mean / median / std | float | Central tendency and dispersion. |
| min / max | float | Extremes. |
| cv | float | Coefficient of variation (std ÷ mean). |
| skewness | float | Distribution asymmetry. 0 is symmetric; positive means a long right tail. |
| kurtosis | float | Excess kurtosis (tailed-ness relative to normal; 0 is normal). |
| q25 / q75 / iqr | float | 25th and 75th percentiles and their difference (interquartile range). |
detailed.analysis.intermittency
| Field | Type | Description |
|---|---|---|
| zero_ratio | float | Proportion of zero values (0–1). |
| cv2 | float | Squared coefficient of variation. > 0.49 signals erratic demand size. |
| adi | float | Average Demand Interval — mean gap between non-zero demands. > 1.32 signals intermittent demand. |
| avg_interval | float | Alias of adi, kept for backward compatibility. |
| is_intermittent | boolean | True when zeros are frequent or demands are widely spaced. |
| demand_pattern | string | Syntetos–Boylan class: smooth, intermittent, erratic, or lumpy. |
detailed.analysis.seasonality
| Field | Type | Description |
|---|---|---|
| has_seasonality | boolean | True when STL or ACF/PACF detected a repeating cycle. |
| seasonal_strength | float | STL seasonal strength, 0–1. |
| trend_strength | float | STL trend strength, 0–1. |
| seasonal_periods | array<integer> | All detected cycle lengths (deduplicated, sorted). |
| primary_period | integer | null | The dominant cycle length, or null if none stands out. |
| stl_analysis / acf_pacf_analysis | object | The raw per-method outputs behind the combined result, for callers that want the details. |
| recommendation | object | model_suggestions (array), use_seasonal_models (bool), and suggested_periods (int, array, or null). |
detailed.analysis.trend_analysis
| Field | Type | Description |
|---|---|---|
| has_trend | boolean | True when trend_strength > 0.3. |
| trend_direction | string | increasing, decreasing, or none. |
| trend_strength / trend_significance | float | Absolute correlation of value with time, 0–1. |
| slope | float | Best-fit linear slope — average change per period. |
| trend_consistency | float | How linear the trend is (0–1); higher means a cleaner straight line. |
| correlation_with_time | float | Signed correlation, −1 to 1. |
detailed.analysis.pattern_classification
| Field | Type | Description |
|---|---|---|
| pattern_type | string | Combined label, e.g. regular, trending, seasonal, seasonal_trending, smooth_intermittent, erratic_intermittent, intermittent_seasonal, intermittent_trending, or complex_intermittent_seasonal_trending. |
| pattern_characteristics | array<string> | Plain-language traits, e.g. "increasing trend", "seasonal". |
| complexity_level | string | low, medium, or high. |
| complexity_score | integer | Numeric score behind complexity_level. |
| forecasting_difficulty | string | easy, medium, or hard. |
| is_intermittent / has_seasonality / has_trend | boolean | The three signals folded into the classification. |
| recommended_approach | object | primary_methods (array) plus a reasoning string for the recommendation. |
detailed.analysis.data_characteristics & external_features
| Field | Type | Description |
|---|---|---|
| data_characteristics | object | Static priors for your data_type: intermittent_likely, seasonal_patterns, trend_common, outliers_common, and preferred_models. These describe what's typical for the type, not what was measured in your series. |
| external_features | object | has_regressors (bool), regressor_cols (array) and regressor_count (int). Zero/empty unless external features were supplied. |
detailed.data_info
| Field | Type | Description |
|---|---|---|
| original_data_points | integer | Points received after parsing. |
| aggregated_points | integer | Points remaining after intelligent aggregation. |
| aggregation_enabled | boolean | Whether aggregation ran (mirrors enable_intelligent_aggregation). |
| data_reduction_ratio | float | Percentage of points removed by aggregation (0 when nothing was rolled up). |
| date_range | object | start and end dates (YYYY-MM-DD) of the analyzed series. |
| value_stats | object | min, max, mean, std, zero_count, and non_zero_count of the analyzed values. |
Response Fields — meta
| Field | Type | Description |
|---|---|---|
| meta.timing.validation | float | Time spent validating the request, in milliseconds. |
| meta.timing.analysing | float | Time spent running the analysis, in milliseconds. |
| meta.timing.total | float | End-to-end request time, in milliseconds. |
Errors
HTTP Status Codes
Validation Error Format
{
"message": "Data points exceed the maximum allowed for your current plan (1000).",
"errors": {
"data": ["Data points exceed the maximum allowed for your current plan (1000)."]
}
}
Partial Failure
The two analyses are computed independently. If the statistical engine is unavailable or errors on your data,
the request still returns 200 with a populated
result.generic, while
result.detailed carries a structured
error object instead of the analysis. Always check for it before reading detailed fields.
{
"result": {
"generic": { "pattern_type": "regular" },
"detailed": {
"error": {
"source": "statsforecast",
"code": "statsforecast_empty_payload",
"message": "StatsForecast analyze returned no analysis results",
"details": {}
}
}
},
"meta": {
"timing": { "validation": 5.1, "analysing": 42.0, "total": 47.4 }
}
}