# API Response Watcher (`marco.gullo/api-response-watcher`) Actor

Checks if some API endpoint's response has changed. Works by creating and storing a JSON schema from the endpoint's response and using it to validate the next response. Depending on the configuration, the stored JSON schema can be updated every time the response changes.

- **URL**: https://apify.com/marco.gullo/api-response-watcher.md
- **Developed by:** [Marco Gullo](https://apify.com/marco.gullo) (community)
- **Categories:** Automation, Developer tools, Integrations
- **Stats:** 10 total users, 0 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## API Response Watcher

A utility to check if some API endpoint's response has changed.

### Purpose

This Actor allows to to test a list of endpoints to check if their response change over time.
For example, you may want to test some API you are using to scrape the content of a website and be notified if some data
has been added, changed, or removed to a specific endpoint.

### How it works, and how to use it

The first time you test an endpoint, the Actor creates a JSON schema out of its response and stores it in the Key-value store.
You can specify the name or the ID of the store you want to use for this, into "Next Key-value store", in the Actor's input.
For more information about Key-value stores, see [here](https://docs.apify.com/platform/storage/key-value-store).

The next time you want to test the same endpoint, you can move the Key-value store ID from "Next Key-value store" to "Previous Key-value store":
in this way, the Actor will use the previously generated JSON schema to validate the new response.
If the validation fails, it will output the differences.

Finally, the Actor will regenerate the JSON schema out of the new answer, merge it with the old schema and save it in the
Key-value store pointed by "Next Key-value store".
If the previous and next stores are the same, the old schema will be overwritten.

### Input

Here is a sample input:

```json
{
    "endpoints": [
        "https://dummyjson.com/products",
        "curl 'https://dummyjson.com/carts' -H 'Accept: application/json'"
    ],
    "prevKvsName": "abcde12345",
    "nextKvsName": "schemas",
    "noRequired": false,
    "doMergeSchemas": true,
    "reportEmail": "my.email@apify.com",
    "reportSlackChannel": "#api-watcher",
    "reportSlackToken": "*******"
}
```

#### Some notes

- The Actor will test two endpoints: `https://dummyjson.com/products` and `https://dummyjson.com/carts`:
  - the first endpoint is a simple public endpoint's URL, which will be contacted through a `GET` request;
  - the second endpoint is actually a cURL command, which will be parsed. You can use the cURL syntax to describe more complex requests, for example ones using a method different from `GET`, custom headers, and custom payload.
- The Actor will validate their responses against some JSON schema from the Key-value store `abcde12345`, if found.
- The Actor will store the updated schemas in a named Key-value store called `schemas`. Each endpoint will have a record key in the Key-value store based on its URL.
- While generating and updating the JSON schema, the Actor will mark all the JSON properties as "required", because `noRequired` is set to `false`.
- The Actor will merge the old and new schemas before storing it into the Key-value store, because `doMergeSchema` is `true`.
- At the end of the Run, if some differences were found:
  - an email will be sent to `my.email@apify.com`, calling the Actor `apify/send-mail`, with a link to the Run default dataset, where those differences were stored;
  - a message will be written to `#api-watcher`, calling the Actor `katerinahronik/slack-message` and using the given token.

#### Further considerations

- The Actor is able to **automatically distinguish** between store names and IDs.
- If you don't specify a "Next Key-value store" in the input, the Actor **won't perform validation**.
- If you don't specify a "Previous Key-value store" in the input, the Actor will still store the generated schema into the **default, unnamed storage**. Unnamed storage has an **expiration** date: for more information, see [here](https://docs.apify.com/platform/storage/usage#named-and-unnamed-storages).

### Output

Here is a sample output:

```json
{
	"url": "https://some-api/data/1",
	"data": {
        "id": 1
    },
	"prevSchema": {
		"type": "object",
		"properties": {
			"id": {
                "type": "string"
            }
		}
	},
	"nextSchema": {
		"type": "object",
		"properties": {
			"id": {
                "type": "integer"
            }
		}
	},
	"mergedSchema": {
		"type": "object",
		"properties": {
			"id": {
                "type": [
                    "integer",
                    "string"
                ]
            }
		}
	},
	"validationErrors": [
		{
			"instancePath": "/id",
			"schemaPath": "#/properties/id/type",
			"message": "must be string"
		}
	]
}
```

The `id` in the response, which was previously a string, is now an integer.
The old and new schemas were merged, because `doMergeSchema` in the input was `true`.
The merged schema admits bot a string and an integer as `id`, so, if it will be used to validate the next Run, both types will pass the validation.

### How to monitor some endpoints

You can leverage Apify's [schedules](https://docs.apify.com/platform/schedules).
Just create a Task with the desired input and run it periodically: you can set it up to receive a notification when some changes are detected.

If you set the same previous and next Key-value stores, the reference schema will be updated every time, so that you will be notified just once when a change is detected.

Or, if you prefer, you can set two different values for the two stores, even leaving the next Key-value store blank, and the change will be detected every time, until you manually update the reference schema.

# Actor input Schema

## `endpoints` (type: `array`):

You can enter URLs or cURL commands.

## `prevKvsName` (type: `string`):

The name or ID of the Key-value store were the previous schema was saved.

## `nextKvsName` (type: `string`):

The name or ID of the Key-value store to save the API response's schema. Can be the same as the previous schema, in which case the previous schema will be overwritten.

## `noRequired` (type: `boolean`):

Do not mark the response's properties as "required", meaning that, if the next response won't have some of those properties, it won't make the validation fail.

## `doMergeSchemas` (type: `boolean`):

Merge old and new schemas after the validation. So, for instance, if a value was an integer before and now it is a string, the next time either an integer or a string will be accepted. It does not influence the current validation.

## `reportEmail` (type: `string`):

Address to send an email to when some changes in the API are detected.

## `reportSlackChannel` (type: `string`):

Slack channel to send a message to when some changes in the API are detected. Format: `#name`.

## `reportSlackToken` (type: `string`):

It is necessary to send a notification on Slack. format: `xoxp-xxxxxxxxx-xxxx`.

## Actor input object example

```json
{
  "endpoints": [
    "https://dummyjson.com/products",
    "curl 'https://dummyjson.com/carts' -H 'Accept: application/json'"
  ],
  "noRequired": false,
  "doMergeSchemas": true
}
```

# 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 = {
    "endpoints": [
        "https://dummyjson.com/products",
        "curl 'https://dummyjson.com/carts' -H 'Accept: application/json'"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("marco.gullo/api-response-watcher").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 = { "endpoints": [
        "https://dummyjson.com/products",
        "curl 'https://dummyjson.com/carts' -H 'Accept: application/json'",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("marco.gullo/api-response-watcher").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 '{
  "endpoints": [
    "https://dummyjson.com/products",
    "curl '\''https://dummyjson.com/carts'\'' -H '\''Accept: application/json'\''"
  ]
}' |
apify call marco.gullo/api-response-watcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,marco.gullo/api-response-watcher"
        }
    }
}

```

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/zUU5wekm7hz5LZbE6/builds/4XUT3dvYAbgdcQSZt/openapi.json
