# Cusp: Google Trends Spike & Trend Detector (Any Dataset) (`aidaabassilova/cusp`) Actor

Find which Google Trends keywords are really taking off, which prices just dropped, and which scrapers silently broke. Point Cusp at any dataset with a date and a number; every finding is computed from your data with the evidence attached. No AI.

- **URL**: https://apify.com/aidaabassilova/cusp.md
- **Developed by:** [Аида Абасилова](https://apify.com/aidaabassilova) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 series analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Cusp — Google Trends Spike & Trend Detector (works on any scraper dataset)

**Run Google Trends Scraper. Cusp tells you which keywords are really taking off — in plain English, with the numbers to prove it.**

It works the same on any dataset with a date and a number: competitor prices, review counts, rankings, followers. And it warns you when a scraper silently starts returning empty data.

#### Try it in 10 seconds

Open the Input tab and run with the example data already filled in — 24 weeks of search interest for one keyword. Cusp returns:

> *Interest settled at a new level around 41 (+78% from about 23), holding for 8 weeks since 2026-06-21. 44 on 2026-08-09 is also the highest of all 24 readings.*

Other real outputs, on sample data:

> *Price fell 19% below the 13-day median (15,990 → 12,990) on 2026-09-14. 12,990 is also the lowest of all 14 readings.*

> *The last 14 days are exactly 0, after a typical level of 6. The data source may have stopped reporting — check the scraper, tracking or integration.*

Every observation is **calculated from your numbers** and written from a fixed template — no language model is involved, so Cusp **cannot report a trend that is not in the data**. The statistics behind each finding are attached so you can check them.

#### Cusp vs. the alternatives

| | Cusp | Eyeballing a chart / a spreadsheet | Asking a chatbot |
|---|---|---|---|
| 100 keywords or products at once | ✅ one run | ❌ one by one | ⚠️ long tables get misread |
| Same answer every time | ✅ | ⚠️ depends who looks | ❌ varies run to run |
| Ignores normal noise and weekly cycles | ✅ calibrated: ≤ 5% false alarms on 60-point series of pure noise | ❌ | ❌ |
| Runs on a schedule after your scraper | ✅ Integrations + history | ❌ | ❌ |
| Shows the evidence (baseline, z-score, p-value) | ✅ | — | ❌ |

### What does Cusp find?

| Type | What it means | Example |
|---|---|---|
| `spike` | A value far above its recent normal level | *Sessions rose 225% above the 14-day median (123 → 400) on 2026-09-14.* |
| `drop` | A value far below its recent normal level | *Price fell 19% below the 13-day median (15,990 → 12,990).* |
| `sustained_trend` | A run of consecutive rises/falls, or a statistically significant trend (Mann–Kendall) | *Interest shows a statistically significant upward trend over the last 30 weeks: about +1.15 per week, +70% relative to its median of 47.5 (Mann–Kendall p < 0.001).* |
| `level_shift` | A step change to a new plateau that holds — a "new normal", not a one-day blip | *Sessions settled at a new level around 600 (−50% from about 1,203), holding for 7 days.* |
| `record` | A new all-time high or low | *Price reached 12,990 — the lowest of all 14 readings.* |
| `reversal` | A streak of gains (or losses) that just turned around | *Value fell 16% to 27, after 6 consecutive gains.* |
| `data_stopped` | The series ends in zeros or missing values — usually a broken scraper, tag or integration | *The last 3 days have no numeric value, after 11 readings that did.* |

Cusp is deliberately **conservative**. It compares weekdays with the same weekday (a normal Saturday dip is not a "drop"), uses Poisson tests for low-volume counts (4 reviews instead of the usual 1 is not a "spike"), leaves out unfinished periods, and with too few points it reports **nothing** rather than guessing.

Because the output is verified facts, Cusp is also a good **tool for AI agents**: the agent calls Cusp and writes its answer from computed numbers (see [Use from AI agents (MCP)](#use-from-ai-agents-mcp)).

### How to use it

1. Run any scraper (Google Trends, Amazon, Kaspi, Google Maps reviews, Instagram followers…).
2. Open Cusp, pick the scraper's dataset, and tell it which field holds the **date** and which holds the **number**. Use **One series per…** when the dataset covers many products, places or keywords.
3. Run. You get a dataset of observations (one row each) and a Markdown **REPORT** in the key-value store.

Cusp never scrapes anything itself, so it doesn't break when websites change.

### Recipes

#### Google Trends spike & trend detector

Run [Google Trends Scraper](https://apify.com/apify/google-trends-scraper), then:

```json
{
  "datasets": ["<Google Trends Scraper dataset>"],
  "unwindField": "interestOverTime_timelineData",
  "dateField": "time",
  "valueField": "value.0",
  "groupBy": "searchTerm",
  "valueLabel": "Interest"
}
```

The unfinished current week (`isPartial`) is skipped automatically, so it's never mistaken for a drop.

#### Price drop alerts & price change monitoring

Most price scrapers (Amazon, Kaspi.kz, Shopify, eBay…) output one snapshot per run without a date. Pick the datasets of several runs and use each dataset's creation time as the date:

```json
{
  "datasets": ["<run 1>", "<run 2>", "<run 3>", "…"],
  "dateSource": "datasetCreatedAt",
  "valueField": "price",
  "groupBy": "title",
  "interval": "day",
  "valueLabel": "Price"
}
```

Prices like `"1,299.00"`, `"$4.50"` or `"12 990 ₸"` are read correctly. A value that is missing or not a number is treated as missing, never as 0. When the price goes missing for several runs in a row, you get a `data_stopped` alert: the scraper probably broke.

**Monitoring on autopilot:** schedule your scraper, open its **Integrations** tab and add Cusp to run when a run succeeds, with this input:

```json
{
  "datasetId": "{{resource.defaultDatasetId}}",
  "dateSource": "datasetCreatedAt",
  "valueField": "price",
  "groupBy": "title",
  "historyKey": "my-prices"
}
```

Cusp keeps the history in your account, adds only points newer than the ones it already stored, and analyzes the whole history on every run. Filter the dataset by `severity` or `record` to catch "lowest price ever".

#### Review velocity monitor (Google Maps, Trustpilot, app stores)

Turn a list of reviews into reviews per week per place, and find sudden surges (a viral post, or fake reviews) or silence:

```json
{
  "datasets": ["<reviews dataset>"],
  "dateField": "publishedAtDate",
  "groupBy": "title",
  "aggregation": "count",
  "interval": "week",
  "endDate": "2026-09-28",
  "valueLabel": "Reviews"
}
```

`endDate` is the date the data was collected. Weeks with no reviews up to that date count as 0, and the week still in progress is left out.

#### Broken scraper detector

Run Cusp on your scraper's output with `historyKey` on a schedule. When a field starts coming back empty or a count drops to zero, you get a `data_stopped` observation with `high` severity, often days before someone notices by hand.

#### Any metric over time

Followers, rankings, stock levels, API usage, sales. Anything with a date and a number works. You can also paste the numbers directly:

```json
{ "data": [{ "date": "2026-09-01", "value": 120 }, { "date": "2026-09-02", "value": 118 }] }
```

or even `{ "data": [120, 118, 125, 410] }` (analyzed in order).

### Input

| Field | What it does | Default |
|---|---|---|
| `datasets` | Datasets to read (several = one combined table) | — |
| `datasetId` | One dataset, for integrations: `{{resource.defaultDatasetId}}` | — |
| `data` | Inline data for trying Cusp: objects, `[date, value]` pairs or plain numbers. Ignored when a dataset is given | example |
| `dateField` | Field with the date/time (ISO, Unix seconds or ms). Dots for nested fields | `date` |
| `valueField` | Field with the number. `value.0` addresses array items | `value` |
| `groupBy` | Separate series per distinct value of this field | one series |
| `unwindField` | A list field whose elements become rows (e.g. Google Trends timeline) | — |
| `dateSource` | `field`, `datasetCreatedAt` (one run = one point) or `order` | `field` |
| `interval` | Bucket rows by `hour`, `day`, `week` (Monday start), `month`, or `none` | `none` |
| `aggregation` | Combine rows in a bucket: `last`, `first`, `sum`, `mean`, `min`, `max`, `count` | `last` |
| `endDate` | For `sum`/`count`: when the data was collected, to count quiet periods as 0 | — |
| `historyKey` | Remember points between runs under this name (monitoring) | — |
| `sensitivity` | `low`, `medium`, `high` | `medium` |
| `minSeverity` | `low` = everything; `medium` or `high` for alerts | `low` |
| `baselineWindow` | Points in the rolling baseline | 14 |
| `shiftWindow` | Points that must hold a new level to call it a "new normal" | 5 |
| `recentPoints` | Look for spikes, drops and records in the last N points (0 = all) | 7 |
| `trendWindow` | Points used by the trend test | 30 |
| `minPoints` | Fewer usable points → not analyzed (and not charged) | 8 |
| `includeQuietSeries` | Add a `no_change` / `insufficient_data` row for such series | true |
| `skipPartialRows` | Skip rows a scraper marks as unfinished (e.g. Google Trends `isPartial`) | true |
| `valueLabel` | Word used in the sentences ("Price", "Interest") | from `valueField` |
| `maxSeries` | Stop after this many series | 5,000 |
| `maxItems` | Stop reading after this many rows (500,000 max at the default 256 MB) | 200,000 |

If a field name is wrong, the run stops with a message listing the fields that do exist in your data.

### Output

One dataset row per observation:

```json
{
  "seriesKey": "iPhone 15 128GB",
  "type": "drop",
  "direction": "down",
  "severity": "medium",
  "startDate": "2026-09-27",
  "endDate": "2026-09-27",
  "value": 12990,
  "baseline": 15990,
  "changePercent": -18.8,
  "record": "low",
  "explanation": "Price fell 19% below the 13-day median (15,990 → 12,990) on 2026-09-27. 12,990 on 2026-09-27 is also the lowest of all 14 readings (previous low 15,990 on 2026-09-14).",
  "evidence": {
    "method": "rolling_median_robust_z",
    "zScore": -31.12,
    "baselineWasFlat": false,
    "baselineMedian": 15990,
    "baselineSpread": 96.4076923077,
    "baselineWindow": 13,
    "sameSeasonMedian": 15990,
    "threshold": "stable",
    "consecutivePoints": 1,
    "peakDate": "2026-09-27",
    "previousRecord": 15990,
    "previousRecordDate": "2026-09-14"
  }
}
```

- `severity`: `high`, `medium` or `low`, so you can filter to what matters.
- `record`: `high`/`low` when the value is also an all-time extreme, whatever the `type`.
- `evidence`: the statistics behind the finding (z-score, baseline, p-value, run length…).

The key-value store also gets **`REPORT`**, a readable Markdown summary with the most important series first, and **`SUMMARY`**, JSON with per-series stats.

### Use from AI agents (MCP)

Cusp works as a tool in Claude, Cursor, VS Code and any other MCP client through the [Apify MCP server](https://mcp.apify.com):

```
https://mcp.apify.com?tools=AidaAbassilova/cusp
```

Add it next to your scraper tools, e.g. `?tools=apify/google-trends-scraper,AidaAbassilova/cusp`, and ask: *"Get Google Trends for these 5 keywords and tell me which ones are really taking off."* The agent fetches the data, calls Cusp, and answers from computed facts instead of eyeballing a table.

For agents, the simplest call passes numbers inline: `{"data": [{"date": "2026-09-01", "value": 12}, …]}`.

### Pricing

**$0.003 per series analyzed.** A series is one keyword, one product or one place. Nothing else is billed per row or per finding.

| What you check | One run | Daily for a month |
|---|---|---|
| 5 Google Trends keywords | $0.015 | about $0.45 |
| 100 products | $0.30 | about $9 |
| 1,000 products | $3 | about $90 |

- Every analyzed series is billed once per run, **whether or not it has findings** — no findings is also an answer.
- Series with too little data (fewer than `minPoints`) are not analyzed and **cost nothing**.
- If the run is restarted by the platform, series already finished are **not billed again**.
- Cusp needs 256 MB and runs in seconds, so platform usage stays small. You can cap a run's cost with the maximum charge setting.

### How it works

- **Spikes and drops:** each recent point is compared with the median of the previous `baselineWindow` points using a robust z-score (median absolute deviation). It must also clear a minimum relative change, a larger one for volatile series and a smaller one for very stable ones like prices, and it must also be unusual compared with the same weekday (daily data) or hour (hourly data). Low-volume counts must also pass a Poisson test.
- **Trends:** a run of at least 5 consecutive moves in one direction (medium sensitivity), or a Mann–Kendall test with a Theil–Sen slope over the last `trendWindow` points. Both need a move that is large next to the series' noise before they rate above low.
- **Calibration** (synthetic daily series, checked in the test suite on every change):
  - pure noise — Poisson counts, ±10% and ±30% noise, weekly cycles: a medium or high finding in at most 5% of 60-point series and at most 7% of 14–30-point series. Shorter history means less certainty, so give Cusp at least a month of data for monitoring;
  - a step is reported as a level shift reliably (≥ 95%) when it is about 3× the day-to-day noise or more — e.g. −20% on ±3% noise, −30% on ±5%, −50% on ±10%. A +40% step on ±10% noise is a level shift in ~80% of cases and still a medium/high finding in ≥ 95%. Smaller steps on noisier data may show up only as a drop or a trend.
- **Level shifts:** Cusp tries every split of the recent stretch into an "old level" and a "new level" and keeps the split that two medians describe best. It reports a shift only if the jump is well beyond the noise on both sides, the new level held for at least `shiftWindow` points, both levels are flat, and two levels fit clearly better than one straight line (a ramp is a trend, not a step). The overlapping spike/drop/trend is folded in so the same event is reported once. From a zero baseline (a new product, a page that just started getting reviews) there is no percentage to compare, so Cusp asks instead whether the old rate could have produced that much (Poisson) and reports a new level of at least 3.
- **Records:** a strict new all-time high or low within the recent points. Ties don't count, and a record only gets medium/high severity if it beats the old one by more than the series' usual wobble.
- **Reversals:** a run of at least 5 moves that the latest one or two moves turned against by at least 10%.
- **Data stopped:** trailing missing values or zeros after a clearly non-zero history. For counts, the number of zeros needed depends on volume (Poisson).

### FAQ

**Does Cusp scrape websites?** No. It only analyzes data you already have, so it doesn't break when sites change and needs no proxies.

**Is my data sent to an AI model?** No. Everything is computed inside the run.

**Can it forecast?** No, on purpose. Cusp reports what the data shows, not predictions.

**What if my dates are in another format?** ISO dates, date-times and Unix timestamps work out of the box. If your dataset has no dates, use `dateSource: "datasetCreatedAt"` (one run = one point) or `"order"`.

**Found a bug or need a new recipe?** Open an issue in the Issues tab. Real-world datasets that trip Cusp up are the most useful reports.

# Actor input Schema

## `datasets` (type: `array`):

One or more datasets, e.g. the output of a price scraper, Google Trends Scraper or a reviews scraper. Several datasets are read one after another as one table — pick the datasets of several runs to build a history.

## `datasetId` (type: `string`):

For automation: when Cusp runs after your scraper via Integrations, set this to {{resource.defaultDatasetId}} and it analyzes that run's dataset. For manual runs use "Datasets to analyze" above.

## `data` (type: `array`):

Your own rows, for trying Cusp or for small one-off checks. Objects like {"date": "2026-09-01", "value": 12}, \[date, value] pairs, or plain numbers. Ignored when you also pick a dataset — so the example rows here never mix into your data.

## `historyKey` (type: `string`):

Give the history a name, e.g. "kaspi-prices", and Cusp stores every run's new points in your account and analyzes the whole history each time. Use it when Cusp runs after each scraper run (schedule or integration), where every run brings only the latest snapshot. Only points newer than what is stored are added, and datasets already added are skipped, so retries never double-count.

## `dateField` (type: `string`):

Field with the date or time of each item. ISO dates, Unix timestamps (seconds or ms) and date-times all work. Use dots for nested fields, e.g. "meta.scrapedAt".

## `valueField` (type: `string`):

Field with the number to analyze, e.g. "price", "rating", "reviewsCount". Formatted strings such as "1,299.00" or "12 990 ₸" are read correctly. Array items are addressed by index: "value.0". Ignored when Aggregation is "count".

## `groupBy` (type: `string`):

Analyze a separate series for each distinct value of this field — e.g. "productId", "url", "searchTerm", "placeId". Leave empty to analyze everything as one series.

## `unwindField` (type: `string`):

When each item holds its history in a list, name that list here and every element becomes a row (with the parent's fields still available for grouping). For Google Trends Scraper output use "interestOverTime_timelineData" with Date field "time", Value field "value.0" and One series per "searchTerm".

## `dateSource` (type: `string`):

"field": read the Date field. "datasetCreatedAt": use each dataset's creation time — perfect when every dataset is one snapshot (one scheduled run of a scraper that has no date in its items). "order": no dates, use the order of items.

## `interval` (type: `string`):

Bucket rows into hours, days, weeks or months before analysis. "none" keeps each timestamp as its own point. Use "day" or "week" with Aggregation "count" to turn a list of reviews or mentions into a volume per day or week.

## `aggregation` (type: `string`):

How to combine several rows that fall into the same bucket. "count" counts rows (reviews per day, mentions per week) and needs no Value field. Empty buckets count as 0 only for "sum" and "count"; for levels like prices they stay missing — never invented.

## `endDate` (type: `string`):

Optional, for "sum" / "count": extend the series with empty (0) buckets up to this date, so that "no new reviews for 5 days" becomes visible. Usually today's date, e.g. 2026-09-28.

## `sensitivity` (type: `string`):

"low" reports only large, unmistakable changes. "high" also reports smaller ones. "medium" is a balanced default.

## `minSeverity` (type: `string`):

For monitoring and alerts: "medium" hides minor findings, "high" keeps only the big ones. Leave empty to report everything.

## `baselineWindow` (type: `integer`):

How many previous points form the 'normal' level a new point is compared against (median). 14 means: compare to the median of the last 14 points.

## `recentPoints` (type: `integer`):

Only the most recent points are checked for spikes, drops and new records, so you hear about what is happening now. 0 = scan the whole history.

## `trendWindow` (type: `integer`):

How many of the latest points the statistical trend test (Mann–Kendall) looks at.

## `shiftWindow` (type: `integer`):

How many of the latest points must hold a new level to report it as a "new normal" (a step change, not a one-off spike).

## `minPoints` (type: `integer`):

Series with fewer usable points are not analyzed at all (and not charged) — Cusp reports nothing rather than guessing.

## `valueLabel` (type: `string`):

E.g. "Price" → "Price fell 19% below the 14-day median…". Defaults to the Value field name.

## `skipPartialRows` (type: `boolean`):

Skip rows marked isPartial: true — Google Trends marks the current, unfinished week this way, and it would always look like a drop.

## `includeQuietSeries` (type: `boolean`):

Adds a "no_change" or "insufficient_data" row for each such series, so every series appears in the output. Turn off to get only real observations. Note: every analyzed series is billed once, whether or not it has findings.

## `maxSeries` (type: `integer`):

Safety limit on how many series (groups) are analyzed in one run.

## `maxItems` (type: `integer`):

Safety limit on how many dataset items are read in total. Up to 500,000 items fit in the default 256 MB of memory; for more, split the run or raise the run's memory.

## Actor input object example

```json
{
  "data": [
    {
      "date": "2026-03-01",
      "value": 22
    },
    {
      "date": "2026-03-08",
      "value": 24
    },
    {
      "date": "2026-03-15",
      "value": 21
    },
    {
      "date": "2026-03-22",
      "value": 23
    },
    {
      "date": "2026-03-29",
      "value": 25
    },
    {
      "date": "2026-04-05",
      "value": 22
    },
    {
      "date": "2026-04-12",
      "value": 24
    },
    {
      "date": "2026-04-19",
      "value": 23
    },
    {
      "date": "2026-04-26",
      "value": 21
    },
    {
      "date": "2026-05-03",
      "value": 24
    },
    {
      "date": "2026-05-10",
      "value": 23
    },
    {
      "date": "2026-05-17",
      "value": 22
    },
    {
      "date": "2026-05-24",
      "value": 25
    },
    {
      "date": "2026-05-31",
      "value": 23
    },
    {
      "date": "2026-06-07",
      "value": 24
    },
    {
      "date": "2026-06-14",
      "value": 22
    },
    {
      "date": "2026-06-21",
      "value": 38
    },
    {
      "date": "2026-06-28",
      "value": 41
    },
    {
      "date": "2026-07-05",
      "value": 39
    },
    {
      "date": "2026-07-12",
      "value": 42
    },
    {
      "date": "2026-07-19",
      "value": 40
    },
    {
      "date": "2026-07-26",
      "value": 43
    },
    {
      "date": "2026-08-02",
      "value": 41
    },
    {
      "date": "2026-08-09",
      "value": 44
    }
  ],
  "dateField": "date",
  "valueField": "value",
  "dateSource": "field",
  "interval": "none",
  "aggregation": "last",
  "sensitivity": "medium",
  "minSeverity": "low",
  "baselineWindow": 14,
  "recentPoints": 7,
  "trendWindow": 30,
  "shiftWindow": 5,
  "minPoints": 8,
  "skipPartialRows": true,
  "includeQuietSeries": true,
  "maxSeries": 5000,
  "maxItems": 200000
}
```

# Actor output Schema

## `observations` (type: `string`):

One row per finding (spike, drop, trend, level shift, record, data stopped, reversal) with the evidence.

## `report` (type: `string`):

All findings as a readable Markdown report.

## `summary` (type: `string`):

Counts of analyzed series and findings by type and severity.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "data": [
        {
            "date": "2026-03-01",
            "value": 22
        },
        {
            "date": "2026-03-08",
            "value": 24
        },
        {
            "date": "2026-03-15",
            "value": 21
        },
        {
            "date": "2026-03-22",
            "value": 23
        },
        {
            "date": "2026-03-29",
            "value": 25
        },
        {
            "date": "2026-04-05",
            "value": 22
        },
        {
            "date": "2026-04-12",
            "value": 24
        },
        {
            "date": "2026-04-19",
            "value": 23
        },
        {
            "date": "2026-04-26",
            "value": 21
        },
        {
            "date": "2026-05-03",
            "value": 24
        },
        {
            "date": "2026-05-10",
            "value": 23
        },
        {
            "date": "2026-05-17",
            "value": 22
        },
        {
            "date": "2026-05-24",
            "value": 25
        },
        {
            "date": "2026-05-31",
            "value": 23
        },
        {
            "date": "2026-06-07",
            "value": 24
        },
        {
            "date": "2026-06-14",
            "value": 22
        },
        {
            "date": "2026-06-21",
            "value": 38
        },
        {
            "date": "2026-06-28",
            "value": 41
        },
        {
            "date": "2026-07-05",
            "value": 39
        },
        {
            "date": "2026-07-12",
            "value": 42
        },
        {
            "date": "2026-07-19",
            "value": 40
        },
        {
            "date": "2026-07-26",
            "value": 43
        },
        {
            "date": "2026-08-02",
            "value": 41
        },
        {
            "date": "2026-08-09",
            "value": 44
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aidaabassilova/cusp").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "data": [
        {
            "date": "2026-03-01",
            "value": 22,
        },
        {
            "date": "2026-03-08",
            "value": 24,
        },
        {
            "date": "2026-03-15",
            "value": 21,
        },
        {
            "date": "2026-03-22",
            "value": 23,
        },
        {
            "date": "2026-03-29",
            "value": 25,
        },
        {
            "date": "2026-04-05",
            "value": 22,
        },
        {
            "date": "2026-04-12",
            "value": 24,
        },
        {
            "date": "2026-04-19",
            "value": 23,
        },
        {
            "date": "2026-04-26",
            "value": 21,
        },
        {
            "date": "2026-05-03",
            "value": 24,
        },
        {
            "date": "2026-05-10",
            "value": 23,
        },
        {
            "date": "2026-05-17",
            "value": 22,
        },
        {
            "date": "2026-05-24",
            "value": 25,
        },
        {
            "date": "2026-05-31",
            "value": 23,
        },
        {
            "date": "2026-06-07",
            "value": 24,
        },
        {
            "date": "2026-06-14",
            "value": 22,
        },
        {
            "date": "2026-06-21",
            "value": 38,
        },
        {
            "date": "2026-06-28",
            "value": 41,
        },
        {
            "date": "2026-07-05",
            "value": 39,
        },
        {
            "date": "2026-07-12",
            "value": 42,
        },
        {
            "date": "2026-07-19",
            "value": 40,
        },
        {
            "date": "2026-07-26",
            "value": 43,
        },
        {
            "date": "2026-08-02",
            "value": 41,
        },
        {
            "date": "2026-08-09",
            "value": 44,
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("aidaabassilova/cusp").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "data": [
    {
      "date": "2026-03-01",
      "value": 22
    },
    {
      "date": "2026-03-08",
      "value": 24
    },
    {
      "date": "2026-03-15",
      "value": 21
    },
    {
      "date": "2026-03-22",
      "value": 23
    },
    {
      "date": "2026-03-29",
      "value": 25
    },
    {
      "date": "2026-04-05",
      "value": 22
    },
    {
      "date": "2026-04-12",
      "value": 24
    },
    {
      "date": "2026-04-19",
      "value": 23
    },
    {
      "date": "2026-04-26",
      "value": 21
    },
    {
      "date": "2026-05-03",
      "value": 24
    },
    {
      "date": "2026-05-10",
      "value": 23
    },
    {
      "date": "2026-05-17",
      "value": 22
    },
    {
      "date": "2026-05-24",
      "value": 25
    },
    {
      "date": "2026-05-31",
      "value": 23
    },
    {
      "date": "2026-06-07",
      "value": 24
    },
    {
      "date": "2026-06-14",
      "value": 22
    },
    {
      "date": "2026-06-21",
      "value": 38
    },
    {
      "date": "2026-06-28",
      "value": 41
    },
    {
      "date": "2026-07-05",
      "value": 39
    },
    {
      "date": "2026-07-12",
      "value": 42
    },
    {
      "date": "2026-07-19",
      "value": 40
    },
    {
      "date": "2026-07-26",
      "value": 43
    },
    {
      "date": "2026-08-02",
      "value": 41
    },
    {
      "date": "2026-08-09",
      "value": 44
    }
  ]
}' |
apify call aidaabassilova/cusp --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aidaabassilova/cusp"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nGD0cGj33YQuM3qHq/builds/wLr9kbWDTi5VDuOBk/openapi.json
