From 3ae07ac6331e93ab1daf4aca7f2200de6ba4b91e Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Sun, 24 Aug 2025 00:48:17 +0200 Subject: [PATCH] API - Use OpenAPI docs (#3384) --- README.md | 5 +- changedetectionio/api/Import.py | 11 +- changedetectionio/api/Notifications.py | 47 +- changedetectionio/api/Search.py | 15 +- changedetectionio/api/SystemInfo.py | 17 +- changedetectionio/api/Tags.py | 75 +- changedetectionio/api/Watch.py | 131 +- docs/README.md | 30 +- docs/api-spec.yaml | 1266 +++++++ docs/api_v1/index.html | 3141 +++++------------ docs/package.json | 12 + docs/python-apidoc/apidoc.py | 397 --- .../changedetection_api_docs.html | 2182 ------------ docs/python-apidoc/introduction.html | 27 - docs/python-apidoc/sidebar-header.html | 1 - docs/python-apidoc/template.html | 506 --- 16 files changed, 2245 insertions(+), 5618 deletions(-) create mode 100644 docs/api-spec.yaml create mode 100644 docs/package.json delete mode 100755 docs/python-apidoc/apidoc.py delete mode 100644 docs/python-apidoc/changedetection_api_docs.html delete mode 100644 docs/python-apidoc/introduction.html delete mode 100644 docs/python-apidoc/sidebar-header.html delete mode 100644 docs/python-apidoc/template.html diff --git a/README.md b/README.md index e3ee3da5..19c7916e 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,10 @@ Excel import is recommended - that way you can better organise tags/groups of we ## API Support -Supports managing the website watch list [via our API](https://changedetection.io/docs/api_v1/index.html) +Full REST API for programmatic management of watches, tags, notifications and more. + +- **[Interactive API Documentation](https://changedetection.io/docs/api_v1/index.html)** - Complete API reference with live testing +- **[OpenAPI Specification](docs/api-spec.yaml)** - Generate SDKs for any programming language ## Support us diff --git a/changedetectionio/api/Import.py b/changedetectionio/api/Import.py index f09f2683..6b34c216 100644 --- a/changedetectionio/api/Import.py +++ b/changedetectionio/api/Import.py @@ -13,16 +13,7 @@ class Import(Resource): @auth.check_token def post(self): - """ - @api {post} /api/v1/import Import a list of watched URLs - @apiDescription Accepts a line-feed separated list of URLs to import, additionally with ?tag_uuids=(tag id), ?tag=(name), ?proxy={key}, ?dedupe=true (default true) one URL per line. - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/import --data-binary @list-of-sites.txt -H"x-api-key:8a111a21bc2f8f1dd9b9353bbd46049a" - @apiName Import - @apiGroup Import - @apiSuccess (200) {List} OK List of watch UUIDs added - @apiSuccess (500) {String} ERR Some other error - """ + """Import a list of watched URLs.""" extras = {} diff --git a/changedetectionio/api/Notifications.py b/changedetectionio/api/Notifications.py index b9e69102..ef2d1954 100644 --- a/changedetectionio/api/Notifications.py +++ b/changedetectionio/api/Notifications.py @@ -13,18 +13,7 @@ class Notifications(Resource): @auth.check_token def get(self): - """ - @api {get} /api/v1/notifications Return Notification URL List - @apiDescription Return the Notification URL List from the configuration - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/notifications -H"x-api-key:813031b16330fe25e3780cf0325daa45" - HTTP/1.0 200 - { - 'notification_urls': ["notification-urls-list"] - } - @apiName Get - @apiGroup Notifications - """ + """Return Notification URL List.""" notification_urls = self.datastore.data.get('settings', {}).get('application', {}).get('notification_urls', []) @@ -35,16 +24,7 @@ class Notifications(Resource): @auth.check_token @expects_json(schema_create_notification_urls) def post(self): - """ - @api {post} /api/v1/notifications Create Notification URLs - @apiDescription Add one or more notification URLs from the configuration - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/notifications/batch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"notification_urls": ["url1", "url2"]}' - @apiName CreateBatch - @apiGroup Notifications - @apiSuccess (201) {Object[]} notification_urls List of added notification URLs - @apiError (400) {String} Invalid input - """ + """Create Notification URLs.""" json_data = request.get_json() notification_urls = json_data.get("notification_urls", []) @@ -71,16 +51,7 @@ class Notifications(Resource): @auth.check_token @expects_json(schema_create_notification_urls) def put(self): - """ - @api {put} /api/v1/notifications Replace Notification URLs - @apiDescription Replace all notification URLs with the provided list (can be empty) - @apiExample {curl} Example usage: - curl -X PUT http://localhost:5000/api/v1/notifications -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"notification_urls": ["url1", "url2"]}' - @apiName Replace - @apiGroup Notifications - @apiSuccess (200) {Object[]} notification_urls List of current notification URLs - @apiError (400) {String} Invalid input - """ + """Replace Notification URLs.""" json_data = request.get_json() notification_urls = json_data.get("notification_urls", []) @@ -102,17 +73,7 @@ class Notifications(Resource): @auth.check_token @expects_json(schema_delete_notification_urls) def delete(self): - """ - @api {delete} /api/v1/notifications Delete Notification URLs - @apiDescription Deletes one or more notification URLs from the configuration - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/notifications -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"notification_urls": ["url1", "url2"]}' - @apiParam {String[]} notification_urls The notification URLs to delete. - @apiName Delete - @apiGroup Notifications - @apiSuccess (204) {String} OK Deleted - @apiError (400) {String} No matching notification URLs found. - """ + """Delete Notification URLs.""" json_data = request.get_json() urls_to_delete = json_data.get("notification_urls", []) diff --git a/changedetectionio/api/Search.py b/changedetectionio/api/Search.py index 84508ecc..3cb62432 100644 --- a/changedetectionio/api/Search.py +++ b/changedetectionio/api/Search.py @@ -9,20 +9,7 @@ class Search(Resource): @auth.check_token def get(self): - """ - @api {get} /api/v1/search Search for watches - @apiDescription Search watches by URL or title text - @apiExample {curl} Example usage: - curl "http://localhost:5000/api/v1/search?q=https://example.com/page1" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - curl "http://localhost:5000/api/v1/search?q=https://example.com/page1?tag=Favourites" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - curl "http://localhost:5000/api/v1/search?q=https://example.com?partial=true" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiName Search - @apiGroup Search - @apiQuery {String} q Search query to match against watch URLs and titles - @apiQuery {String} [tag] Optional name of tag to limit results (name not UUID) - @apiQuery {String} [partial] Allow partial matching of URL query - @apiSuccess (200) {Object} JSON Object containing matched watches - """ + """Search for watches by URL or title text.""" query = request.args.get('q', '').strip() tag_limit = request.args.get('tag', '').strip() from changedetectionio.strtobool import strtobool diff --git a/changedetectionio/api/SystemInfo.py b/changedetectionio/api/SystemInfo.py index 72105b99..3457a39a 100644 --- a/changedetectionio/api/SystemInfo.py +++ b/changedetectionio/api/SystemInfo.py @@ -10,22 +10,7 @@ class SystemInfo(Resource): @auth.check_token def get(self): - """ - @api {get} /api/v1/systeminfo Return system info - @apiDescription Return some info about the current system state - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/systeminfo -H"x-api-key:813031b16330fe25e3780cf0325daa45" - HTTP/1.0 200 - { - 'queue_size': 10 , - 'overdue_watches': ["watch-uuid-list"], - 'uptime': 38344.55, - 'watch_count': 800, - 'version': "0.40.1" - } - @apiName Get Info - @apiGroup System Information - """ + """Return system info.""" import time overdue_watches = [] diff --git a/changedetectionio/api/Tags.py b/changedetectionio/api/Tags.py index 9c9d21ab..f593e1e9 100644 --- a/changedetectionio/api/Tags.py +++ b/changedetectionio/api/Tags.py @@ -20,21 +20,7 @@ class Tag(Resource): # curl http://localhost:5000/api/v1/tag/ @auth.check_token def get(self, uuid): - """ - @api {get} /api/v1/tag/:uuid Single tag / group - Get data, toggle notification muting, recheck all. - @apiDescription Retrieve tag information, set notification_muted status, recheck all in tag. - @apiExampleRequest - curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -H"x-api-key:813031b16330fe25e3780cf0325daa45" - curl "http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091?muted=muted" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - curl "http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091?recheck=true" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiName Tag - @apiGroup Group / Tag - @apiParam {uuid} uuid Tag unique ID. - @apiQuery {String} [muted] =`muted` or =`unmuted` , Sets the MUTE NOTIFICATIONS state - @apiQuery {String} [recheck] = True, Queue all watches with this tag for recheck - @apiSuccess (200) {String} OK When muted operation OR full JSON object of the tag - @apiSuccess (200) {JSON} TagJSON JSON Full JSON object of the tag - """ + """Get data for a single tag/group, toggle notification muting, or recheck all.""" from copy import deepcopy tag = deepcopy(self.datastore.data['settings']['application']['tags'].get(uuid)) if not tag: @@ -65,17 +51,7 @@ class Tag(Resource): @auth.check_token def delete(self, uuid): - """ - @api {delete} /api/v1/tag/:uuid Delete a tag / group and remove it from all watches - @apiExampleRequest {curl} Example usage: - curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiExampleResponse {string} - OK - @apiParam {uuid} uuid Tag unique ID. - @apiName DeleteTag - @apiGroup Group / Tag - @apiSuccess (200) {String} OK Was deleted - """ + """Delete a tag/group and remove it from all watches.""" if not self.datastore.data['settings']['application']['tags'].get(uuid): abort(400, message='No tag exists with the UUID of {}'.format(uuid)) @@ -92,19 +68,7 @@ class Tag(Resource): @auth.check_token @expects_json(schema_update_tag) def put(self, uuid): - """ - @api {put} /api/v1/tag/:uuid Update tag information - @apiExampleRequest {curl} Request: - curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X PUT -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"title": "New Tag Title"}' - @apiExampleResponse {json} Response: - "OK" - @apiDescription Updates an existing tag using JSON - @apiParam {uuid} uuid Tag unique ID. - @apiName UpdateTag - @apiGroup Group / Tag - @apiSuccess (200) {String} OK Was updated - @apiSuccess (500) {String} ERR Some other error - """ + """Update tag information.""" tag = self.datastore.data['settings']['application']['tags'].get(uuid) if not tag: abort(404, message='No tag exists with the UUID of {}'.format(uuid)) @@ -118,15 +82,7 @@ class Tag(Resource): @auth.check_token # Only cares for {'title': 'xxxx'} def post(self): - """ - @api {post} /api/v1/watch Create a single tag / group - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"name": "Work related"}' - @apiName Create - @apiGroup Group / Tag - @apiSuccess (200) {String} OK Was created - @apiSuccess (500) {String} ERR Some other error - """ + """Create a single tag/group.""" json_data = request.get_json() title = json_data.get("title",'').strip() @@ -145,28 +101,7 @@ class Tags(Resource): @auth.check_token def get(self): - """ - @api {get} /api/v1/tags List tags / groups - @apiDescription Return list of available tags / groups - @apiExampleRequest {curl} Request: - curl http://localhost:5000/api/v1/tags -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiExampleResponse {json} Response: - { - "cc0cfffa-f449-477b-83ea-0caafd1dc091": { - "title": "Tech News", - "notification_muted": false, - "date_created": 1677103794 - }, - "e6f5fd5c-dbfe-468b-b8f3-f9d6ff5ad69b": { - "title": "Shopping", - "notification_muted": true, - "date_created": 1676662819 - } - } - @apiName ListTags - @apiGroup Group / Tag Management - @apiSuccess (200) {JSON} Short list of tags keyed by tag/group UUID - """ + """List tags/groups.""" result = {} for uuid, tag in self.datastore.data['settings']['application']['tags'].items(): result[uuid] = { diff --git a/changedetectionio/api/Watch.py b/changedetectionio/api/Watch.py index e6e07ee1..8162a2be 100644 --- a/changedetectionio/api/Watch.py +++ b/changedetectionio/api/Watch.py @@ -26,23 +26,7 @@ class Watch(Resource): # ?recheck=true @auth.check_token def get(self, uuid): - """ - @api {get} /api/v1/watch/:uuid Single watch - get data, recheck, pause, mute. - @apiDescription Retrieve watch information and set muted/paused status, returns the FULL Watch JSON which can be used for any other PUT (update etc) - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091 -H"x-api-key:813031b16330fe25e3780cf0325daa45" - curl "http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091?muted=unmuted" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - curl "http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091?paused=unpaused" -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiName Watch - @apiGroup Watch - @apiGroupDocOrder 0 - @apiParam {uuid} uuid Watch unique ID. - @apiQuery {Boolean} [recheck] Recheck this watch `recheck=1` - @apiQuery {String} [paused] =`paused` or =`unpaused` , Sets the PAUSED state - @apiQuery {String} [muted] =`muted` or =`unmuted` , Sets the MUTE NOTIFICATIONS state - @apiSuccess (200) {String} OK When paused/muted/recheck operation OR full JSON object of the watch - @apiSuccess (200) {JSON} WatchJSON JSON Full JSON object of the watch - """ + """Get information about a single watch, recheck, pause, or mute.""" from copy import deepcopy watch = deepcopy(self.datastore.data['watching'].get(uuid)) if not watch: @@ -74,15 +58,7 @@ class Watch(Resource): @auth.check_token def delete(self, uuid): - """ - @api {delete} /api/v1/watch/:uuid Delete a watch and related history - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiParam {uuid} uuid Watch unique ID. - @apiName Delete - @apiGroup Watch - @apiSuccess (200) {String} OK Was deleted - """ + """Delete a watch and related history.""" if not self.datastore.data['watching'].get(uuid): abort(400, message='No watch exists with the UUID of {}'.format(uuid)) @@ -92,19 +68,7 @@ class Watch(Resource): @auth.check_token @expects_json(schema_update_watch) def put(self, uuid): - """ - @api {put} /api/v1/watch/:uuid Update watch information - @apiExampleRequest {curl} Example usage: - curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X PUT -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"url": "https://my-nice.com" , "tag": "new list"}' - @apiExampleResponse {string} Example usage: - OK - @apiDescription Updates an existing watch using JSON, accepts the same structure as returned in get single watch information - @apiParam {uuid} uuid Watch unique ID. - @apiName Update a watch - @apiGroup Watch - @apiSuccess (200) {String} OK Was updated - @apiSuccess (500) {String} ERR Some other error - """ + """Update watch information.""" watch = self.datastore.data['watching'].get(uuid) if not watch: abort(404, message='No watch exists with the UUID of {}'.format(uuid)) @@ -128,23 +92,7 @@ class WatchHistory(Resource): # curl http://localhost:5000/api/v1/watch//history @auth.check_token def get(self, uuid): - """ - @api {get} /api/v1/watch//history Get a list of all historical snapshots available for a watch - @apiDescription Requires `uuid`, returns list - @apiGroupDocOrder 1 - @apiExampleRequest {curl} Request: - curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" - @apiExampleResponse {json} Response: - { - "1676649279": "/tmp/data/6a4b7d5c-fee4-4616-9f43-4ac97046b595/cb7e9be8258368262246910e6a2a4c30.txt", - "1677092785": "/tmp/data/6a4b7d5c-fee4-4616-9f43-4ac97046b595/e20db368d6fc633e34f559ff67bb4044.txt", - "1677103794": "/tmp/data/6a4b7d5c-fee4-4616-9f43-4ac97046b595/02efdd37dacdae96554a8cc85dc9c945.txt" - } - @apiName Get list of available stored snapshots for watch - @apiGroup Watch History - @apiSuccess (200) {JSON} List of keyed (by change date) paths to snapshot, use the key to fetch a single snapshot. - @apiSuccess (404) {String} ERR Not found - """ + """Get a list of all historical snapshots available for a watch.""" watch = self.datastore.data['watching'].get(uuid) if not watch: abort(404, message='No watch exists with the UUID of {}'.format(uuid)) @@ -158,20 +106,7 @@ class WatchSingleHistory(Resource): @auth.check_token def get(self, uuid, timestamp): - """ - @api {get} /api/v1/watch//history/ Get single snapshot from watch - @apiDescription Requires watch `uuid` and `timestamp`. `timestamp` of "`latest`" for latest available snapshot, or use the list returned here - @apiExampleRequest {curl} Example usage: - curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history/1677092977 -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" - @apiExampleResponse {string} Closes matching snapshot text - Big bad fox flew over the moon at 2025-01-01 etc etc - @apiName Get single snapshot content - @apiGroup Snapshots - @apiGroupDocOrder 2 - @apiParam {String} [html] Optional Set to =1 to return the last HTML (only stores last 2 snapshots, use `latest` as timestamp) - @apiSuccess (200) {String} OK - @apiSuccess (404) {String} ERR Not found - """ + """Get single snapshot from watch.""" watch = self.datastore.data['watching'].get(uuid) if not watch: abort(404, message=f"No watch exists with the UUID of {uuid}") @@ -204,19 +139,7 @@ class WatchFavicon(Resource): @auth.check_token def get(self, uuid): - """ - @api {get} /api/v1/watch//favicon Get favicon for a watch. - @apiDescription Requires watch `uuid`, ,The favicon is the favicon which is available in the page watch overview list. - @apiExampleRequest {curl} Example usage: - curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/favicon -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiExampleResponse {binary data} - JPEG... - @apiName Get latest Favicon - @apiGroup Favicon - @apiGroupDocOrder 3 - @apiSuccess (200) {binary} Data ( Binary data of the favicon ) - @apiSuccess (404) {String} ERR Not found - """ + """Get favicon for a watch.""" watch = self.datastore.data['watching'].get(uuid) if not watch: abort(404, message=f"No watch exists with the UUID of {uuid}") @@ -251,16 +174,7 @@ class CreateWatch(Resource): @auth.check_token @expects_json(schema_create_watch) def post(self): - """ - @api {post} /api/v1/watch Create a single watch - @apiDescription Requires atleast `url` set, can accept the same structure as get single watch information to create. - @apiExample {curl} Example usage: - curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"url": "https://my-nice.com" , "tag": "nice list"}' - @apiName Create - @apiGroup Watch - @apiSuccess (200) {String} OK Was created - @apiSuccess (500) {String} ERR Some other error - """ + """Create a single watch.""" json_data = request.get_json() url = json_data['url'].strip() @@ -294,36 +208,7 @@ class CreateWatch(Resource): @auth.check_token def get(self): - """ - @api {get} /api/v1/watch List watches - @apiDescription Return concise list of available watches and some very basic info - @apiExampleRequest {curl} Request: - curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45" - @apiExampleResponse {json} Response: - { - "6a4b7d5c-fee4-4616-9f43-4ac97046b595": { - "last_changed": 1677103794, - "last_checked": 1677103794, - "last_error": false, - "title": "", - "url": "http://www.quotationspage.com/random.php" - }, - "e6f5fd5c-dbfe-468b-b8f3-f9d6ff5ad69b": { - "last_changed": 0, - "last_checked": 1676662819, - "last_error": false, - "title": "QuickLook", - "url": "https://github.com/QL-Win/QuickLook/tags" - } - } - - @apiParam {String} [recheck_all] Optional Set to =1 to force recheck of all watches - @apiParam {String} [tag] Optional name of tag to limit results - @apiName ListWatches - @apiGroup Watch Management - @apiGroupDocOrder 4 - @apiSuccess (200) {String} OK JSON dict - """ + """List watches.""" list = {} tag_limit = request.args.get('tag', '').lower() diff --git a/docs/README.md b/docs/README.md index 0d874c60..65bb1919 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,33 @@ Directory of docs -To regenerate API docs +## Regenerating API Documentation -Run from this directory. +### Modern Interactive API Docs (Recommended) + +To regenerate the modern API documentation, run from the `docs/` directory: + +```bash +# Install dependencies (first time only) +npm install + +# Generate the HTML documentation from OpenAPI spec using Redoc +npm run build-docs +``` + +### OpenAPI Specification + +The OpenAPI specification (`docs/api-spec.yaml`) is the source of truth for API documentation. This industry-standard format enables: + +- **Interactive documentation** - Test endpoints directly in the browser +- **SDK generation** - Auto-generate client libraries for any programming language +- **API validation** - Ensure code matches documentation +- **Integration tools** - Import into Postman, Insomnia, API gateways, etc. + +**Important:** When adding or modifying API endpoints, you must update `docs/api-spec.yaml` to keep documentation in sync: + +1. Edit `docs/api-spec.yaml` with new endpoints, parameters, or response schemas +2. Run `npm run build-docs` to regenerate the HTML documentation +3. Commit both the YAML spec and generated HTML files -`python3 python-apidoc/apidoc.py -i ../changedetectionio -o api_v1/index.html` diff --git a/docs/api-spec.yaml b/docs/api-spec.yaml new file mode 100644 index 00000000..cb3e9ce2 --- /dev/null +++ b/docs/api-spec.yaml @@ -0,0 +1,1266 @@ +openapi: 3.0.3 +info: + title: ChangeDetection.io API + description: | + # ChangeDetection.io Web page monitoring and notifications API + + REST API for managing Page watches, Group tags, and Notifications. + + changedetection.io can be driven by its built in simple API, in the examples below you will also find `curl` command line examples to help you. + + ## Where to find my API key? + + The API key can be easily found under the **SETTINGS** then **API** tab of changedetection.io dashboard. + Simply click the API key to automatically copy it to your clipboard. + + ![Where to find the API key](./where-to-get-api-key.jpeg) + + ## Connection URL + + The API can be found at `/api/v1/`, so for example if you run changedetection.io locally on port 5000, then URL would be `http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history`. + + If you are using the hosted/subscription version of changedetection.io, then the URL is based on your login URL, for example: + `https:///api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history` + + ## Authentication + + Almost all API requests require some authentication, this is provided as an **API Key** in the header of the HTTP request. + + For example: `x-api-key: YOUR_API_KEY` + + version: 1.0.0 + contact: + name: ChangeDetection.io + url: https://github.com/dgtlmoon/changedetection.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + +servers: + - url: http://localhost:5000/api/v1 + description: Development server + - url: https://yourdomain.com/api/v1 + description: Production server + - url: '{protocol}://{host}/api/v1' + description: Custom server + variables: + protocol: + enum: + - http + - https + default: https + host: + default: yourdomain.com + description: Your changedetection.io host + +security: + - ApiKeyAuth: [] + +tags: + - name: Watch Management + description: | + Core functionality for managing web page monitors. Create, retrieve, update, and delete individual watches. + Each watch represents a single URL being monitored for changes, with configurable settings for check intervals, + notification preferences, and content filtering options. + + - name: Watch History + description: | + Access historical snapshots and change data for your watches. View the complete timeline of detected changes + and retrieve specific versions of monitored content for comparison and analysis. + + - name: Snapshots + description: | + Retrieve individual snapshots of monitored content. Access both the processed change detection data and + the raw HTML content that was captured during monitoring checks. + + - name: Favicon + description: | + Retrieve favicon images associated with monitored web pages. These are used in the dashboard interface + to visually identify different watches in your monitoring list. + + - name: Group / Tag Management + description: | + Organize your watches using tags and groups. Tags (also known as Groups) allow you to categorize monitors, set group-wide + notification preferences, and perform bulk operations like mass rechecking or status changes across + multiple related watches. + + - name: Notifications + description: | + Configure global notification endpoints that can be used across all your watches. Supports various + notification services including email, Discord, Slack, webhooks, and many other popular platforms. + These settings serve as defaults that can be overridden at the individual watch or tag level. + + - name: Search + description: | + Search and filter your watches by URL patterns, titles, or tags. Useful for quickly finding specific + monitors in large collections or identifying watches that match certain criteria. + + - name: Import + description: | + Bulk import multiple URLs for monitoring. Accepts plain text lists of URLs and can automatically + apply tags, proxy settings, and other configurations to all imported watches simultaneously. + + - name: System Information + description: | + Retrieve system status and statistics about your changedetection.io instance, including total watch + counts, uptime information, and version details. + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: x-api-key + description: | + API key for authentication. You can find your API key in the changedetection.io dashboard under Settings > API. + + Enter your API key in the "Authorize" button above to automatically populate all code examples. + + schemas: + Watch: + type: object + properties: + uuid: + type: string + format: uuid + description: Unique identifier for the watch + readOnly: true + url: + type: string + format: uri + description: URL to monitor for changes + maxLength: 5000 + title: + type: string + description: Custom title for the watch + maxLength: 5000 + tag: + type: string + description: Tag UUID to associate with this watch + maxLength: 5000 + tags: + type: array + items: + type: string + description: Array of tag UUIDs + paused: + type: boolean + description: Whether the watch is paused + muted: + type: boolean + description: Whether notifications are muted + method: + type: string + enum: [GET, POST, DELETE, PUT] + description: HTTP method to use + fetch_backend: + type: string + enum: [html_requests, html_webdriver] + description: Backend to use for fetching content + headers: + type: object + additionalProperties: + type: string + description: HTTP headers to include in requests + body: + type: string + description: HTTP request body + maxLength: 5000 + proxy: + type: string + description: Proxy configuration + maxLength: 5000 + webdriver_delay: + type: integer + description: Delay in seconds for webdriver + webdriver_js_execute_code: + type: string + description: JavaScript code to execute + maxLength: 5000 + time_between_check: + type: object + properties: + weeks: + type: integer + days: + type: integer + hours: + type: integer + minutes: + type: integer + seconds: + type: integer + description: Time intervals between checks + notification_urls: + type: array + items: + type: string + description: Notification URLs for this watch + notification_title: + type: string + description: Custom notification title + maxLength: 5000 + notification_body: + type: string + description: Custom notification body + maxLength: 5000 + notification_format: + type: string + enum: [Text, HTML, Markdown] + description: Format for notifications + track_ldjson_price_data: + type: boolean + description: Whether to track JSON-LD price data + browser_steps: + type: array + items: + type: object + properties: + operation: + type: string + maxLength: 5000 + selector: + type: string + maxLength: 5000 + optional_value: + type: string + maxLength: 5000 + required: [operation, selector, optional_value] + description: Browser automation steps + last_checked: + type: integer + description: Unix timestamp of last check + readOnly: true + last_changed: + type: integer + description: Unix timestamp of last change + readOnly: true + last_error: + type: string + description: Last error message + readOnly: true + required: + - url + + CreateWatch: + allOf: + - $ref: '#/components/schemas/Watch' + - type: object + required: + - url + + Tag: + type: object + properties: + uuid: + type: string + format: uuid + description: Unique identifier for the tag + readOnly: true + title: + type: string + description: Tag title + maxLength: 5000 + notification_urls: + type: array + items: + type: string + description: Default notification URLs for watches with this tag + notification_muted: + type: boolean + description: Whether notifications are muted for this tag + required: + - title + + NotificationUrls: + type: object + properties: + notification_urls: + type: array + items: + type: string + format: uri + description: List of notification URLs + required: + - notification_urls + + SystemInfo: + type: object + properties: + watch_count: + type: integer + description: Total number of watches + tag_count: + type: integer + description: Total number of tags + uptime: + type: string + description: System uptime + version: + type: string + description: Application version + + SearchResult: + type: object + properties: + watches: + type: object + additionalProperties: + $ref: '#/components/schemas/Watch' + description: Dictionary of matching watches keyed by UUID + + WatchHistory: + type: object + additionalProperties: + type: string + description: Path to snapshot file + description: Dictionary of timestamps and snapshot paths + + Error: + type: object + properties: + message: + type: string + description: Error message + +paths: + /watch: + get: + tags: [Watch Management] + summary: List all watches + description: Return concise list of available watches and basic info + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/watch" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + response = requests.get('http://localhost:5000/api/v1/watch', headers=headers) + print(response.json()) + parameters: + - name: recheck_all + in: query + description: Set to 1 to force recheck of all watches + schema: + type: string + enum: ["1"] + - name: tag + in: query + description: Tag name to filter results + schema: + type: string + responses: + '200': + description: List of watches + content: + application/json: + schema: + type: object + additionalProperties: + $ref: '#/components/schemas/Watch' + example: + "095be615-a8ad-4c33-8e9c-c7612fbf6c9f": + uuid: "095be615-a8ad-4c33-8e9c-c7612fbf6c9f" + url: "http://example.com" + title: "Example Website Monitor" + tag: "550e8400-e29b-41d4-a716-446655440000" + tags: ["550e8400-e29b-41d4-a716-446655440000"] + paused: false + muted: false + method: "GET" + fetch_backend: "html_requests" + last_checked: 1640995200 + last_changed: 1640995200 + "7c9e6b8d-f2a1-4e5c-9d3b-8a7f6e4c2d1a": + uuid: "7c9e6b8d-f2a1-4e5c-9d3b-8a7f6e4c2d1a" + url: "https://news.example.org" + title: "News Site Tracker" + tag: "330e8400-e29b-41d4-a716-446655440001" + tags: ["330e8400-e29b-41d4-a716-446655440001"] + paused: false + muted: true + method: "GET" + fetch_backend: "html_webdriver" + last_checked: 1640998800 + last_changed: 1640995200 + post: + tags: [Watch Management] + summary: Create a new watch + description: Create a single watch. Requires at least 'url' to be set. + x-code-samples: + - lang: 'curl' + source: | + curl -X POST "http://localhost:5000/api/v1/watch" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "title": "Example Site Monitor", + "time_between_check": { + "hours": 1 + } + }' + - lang: 'Python' + source: | + import requests + import json + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + data = { + 'url': 'https://example.com', + 'title': 'Example Site Monitor', + 'time_between_check': { + 'hours': 1 + } + } + response = requests.post('http://localhost:5000/api/v1/watch', + headers=headers, json=data) + print(response.text) + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateWatch' + example: + url: "https://example.com" + title: "Example Site Monitor" + time_between_check: + hours: 1 + responses: + '200': + description: Watch created successfully + content: + text/plain: + schema: + type: string + example: "OK" + '500': + description: Server error + content: + text/plain: + schema: + type: string + + /watch/{uuid}: + get: + operationId: getSingleWatch + tags: [Watch Management] + summary: Get single watch + description: Retrieve watch information and set muted/paused status. Returns the FULL Watch JSON. + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f' + response = requests.get(f'http://localhost:5000/api/v1/watch/{uuid}', headers=headers) + print(response.json()) + parameters: + - name: uuid + in: path + required: true + description: Watch unique ID + schema: + type: string + format: uuid + - name: recheck + in: query + description: Recheck this watch + schema: + type: string + enum: ["1", "true"] + - name: paused + in: query + description: Set pause state + schema: + type: string + enum: [paused, unpaused] + - name: muted + in: query + description: Set mute state + schema: + type: string + enum: [muted, unmuted] + responses: + '200': + description: Watch information or operation result + content: + application/json: + schema: + $ref: '#/components/schemas/Watch' + text/plain: + schema: + type: string + example: "OK" + '404': + description: Watch not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + put: + operationId: updateWatch + tags: [Watch Management] + summary: Update watch + description: Update an existing watch using JSON. Accepts the same structure as returned in [get single watch information](#operation/getSingleWatch). + x-code-samples: + - lang: 'curl' + source: | + curl -X PUT "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://updated-example.com", + "title": "Updated Monitor", + "paused": false + }' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f' + data = { + 'url': 'https://updated-example.com', + 'title': 'Updated Monitor', + 'paused': False + } + response = requests.put(f'http://localhost:5000/api/v1/watch/{uuid}', + headers=headers, json=data) + print(response.text) + parameters: + - name: uuid + in: path + required: true + description: Watch unique ID + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Watch' + responses: + '200': + description: Watch updated successfully + content: + text/plain: + schema: + type: string + example: "OK" + '500': + description: Server error + + delete: + tags: [Watch Management] + summary: Delete watch + description: Delete a watch and all related history + x-code-samples: + - lang: 'curl' + source: | + curl -X DELETE "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f' + response = requests.delete(f'http://localhost:5000/api/v1/watch/{uuid}', headers=headers) + print(response.text) + parameters: + - name: uuid + in: path + required: true + description: Watch unique ID + schema: + type: string + format: uuid + responses: + '200': + description: Watch deleted successfully + content: + text/plain: + schema: + type: string + example: "OK" + + /watch/{uuid}/history: + get: + tags: [Watch History] + summary: Get watch history + description: Get a list of all historical snapshots available for a watch + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f' + response = requests.get(f'http://localhost:5000/api/v1/watch/{uuid}/history', headers=headers) + print(response.json()) + parameters: + - name: uuid + in: path + required: true + description: Watch unique ID + schema: + type: string + format: uuid + responses: + '200': + description: List of available snapshots + content: + application/json: + schema: + $ref: '#/components/schemas/WatchHistory' + example: + "1640995200": "/path/to/snapshot1.txt" + "1640998800": "/path/to/snapshot2.txt" + '404': + description: Watch not found + + /watch/{uuid}/history/{timestamp}: + get: + tags: [Snapshots] + summary: Get single snapshot + description: Get single snapshot from watch. Use 'latest' for the most recent snapshot. + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history/latest" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f' + timestamp = 'latest' # or use specific timestamp like 1640995200 + response = requests.get(f'http://localhost:5000/api/v1/watch/{uuid}/history/{timestamp}', headers=headers) + print(response.text) + parameters: + - name: uuid + in: path + required: true + description: Watch unique ID + schema: + type: string + format: uuid + - name: timestamp + in: path + required: true + description: Snapshot timestamp or 'latest' + schema: + oneOf: + - type: integer + - type: string + enum: [latest] + - name: html + in: query + description: Set to 1 to return the last HTML + schema: + type: string + enum: ["1"] + responses: + '200': + description: Snapshot content + content: + text/plain: + schema: + type: string + '404': + description: Snapshot not found + + /watch/{uuid}/favicon: + get: + tags: [Favicon] + summary: Get watch favicon + description: Get the favicon for a watch as displayed in the watch overview list. + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/favicon" \ + -H "x-api-key: YOUR_API_KEY" \ + --output favicon.ico + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + uuid = '095be615-a8ad-4c33-8e9c-c7612fbf6c9f' + response = requests.get(f'http://localhost:5000/api/v1/watch/{uuid}/favicon', headers=headers) + with open('favicon.ico', 'wb') as f: + f.write(response.content) + parameters: + - name: uuid + in: path + required: true + description: Watch unique ID + schema: + type: string + format: uuid + responses: + '200': + description: Favicon binary data + content: + image/*: + schema: + type: string + format: binary + '404': + description: Favicon not found + + /tags: + get: + tags: [Group / Tag Management] + summary: List all tags + description: Return list of available tags/groups + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/tags" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + response = requests.get('http://localhost:5000/api/v1/tags', headers=headers) + print(response.json()) + responses: + '200': + description: List of tags + content: + application/json: + schema: + type: object + additionalProperties: + $ref: '#/components/schemas/Tag' + example: + "550e8400-e29b-41d4-a716-446655440000": + uuid: "550e8400-e29b-41d4-a716-446655440000" + title: "Production Sites" + notification_urls: ["mailto:admin@example.com"] + notification_muted: false + "330e8400-e29b-41d4-a716-446655440001": + uuid: "330e8400-e29b-41d4-a716-446655440001" + title: "News Sources" + notification_urls: ["discord://webhook_id/webhook_token"] + notification_muted: false + + /tag/{uuid}: + get: + tags: [Group / Tag Management] + summary: Get single tag + description: Retrieve tag information, set notification_muted status, recheck all in tag. + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + tag_uuid = '550e8400-e29b-41d4-a716-446655440000' + response = requests.get(f'http://localhost:5000/api/v1/tag/{tag_uuid}', headers=headers) + print(response.json()) + parameters: + - name: uuid + in: path + required: true + description: Tag unique ID + schema: + type: string + format: uuid + - name: muted + in: query + description: Set mute state + schema: + type: string + enum: [muted, unmuted] + - name: recheck + in: query + description: Queue all watches with this tag for recheck + schema: + type: string + enum: ["true"] + responses: + '200': + description: Tag information or operation result + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + text/plain: + schema: + type: string + example: "OK" + '404': + description: Tag not found + + put: + tags: [Group / Tag Management] + summary: Update tag + description: Update an existing tag using JSON + x-code-samples: + - lang: 'curl' + source: | + curl -X PUT "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Updated Production Sites", + "notification_muted": false + }' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + tag_uuid = '550e8400-e29b-41d4-a716-446655440000' + data = { + 'title': 'Updated Production Sites', + 'notification_muted': False + } + response = requests.put(f'http://localhost:5000/api/v1/tag/{tag_uuid}', + headers=headers, json=data) + print(response.text) + parameters: + - name: uuid + in: path + required: true + description: Tag unique ID + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + responses: + '200': + description: Tag updated successfully + '500': + description: Server error + + delete: + tags: [Group / Tag Management] + summary: Delete tag + description: Delete a tag/group and remove it from all watches + x-code-samples: + - lang: 'curl' + source: | + curl -X DELETE "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + tag_uuid = '550e8400-e29b-41d4-a716-446655440000' + response = requests.delete(f'http://localhost:5000/api/v1/tag/{tag_uuid}', headers=headers) + print(response.text) + parameters: + - name: uuid + in: path + required: true + description: Tag unique ID + schema: + type: string + format: uuid + responses: + '200': + description: Tag deleted successfully + + post: + tags: [Group / Tag Management] + summary: Create tag + description: Create a single tag/group + x-code-samples: + - lang: 'curl' + source: | + curl -X POST "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Important Sites" + }' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + tag_uuid = '550e8400-e29b-41d4-a716-446655440000' + data = {'title': 'Important Sites'} + response = requests.post(f'http://localhost:5000/api/v1/tag/{tag_uuid}', + headers=headers, json=data) + print(response.text) + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Tag' + example: + title: "Important Sites" + responses: + '200': + description: Tag created successfully + '500': + description: Server error + + /notifications: + get: + tags: [Notifications] + summary: Get notification URLs + description: Return the notification URL list from the configuration + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/notifications" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + response = requests.get('http://localhost:5000/api/v1/notifications', headers=headers) + print(response.json()) + responses: + '200': + description: List of notification URLs + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationUrls' + + post: + tags: [Notifications] + summary: Add notification URLs + description: Add one or more notification URLs to the configuration + x-code-samples: + - lang: 'curl' + source: | + curl -X POST "http://localhost:5000/api/v1/notifications" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "notification_urls": [ + "mailto:admin@example.com", + "discord://webhook_id/webhook_token" + ] + }' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + data = { + 'notification_urls': [ + 'mailto:admin@example.com', + 'discord://webhook_id/webhook_token' + ] + } + response = requests.post('http://localhost:5000/api/v1/notifications', + headers=headers, json=data) + print(response.json()) + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationUrls' + example: + notification_urls: + - "mailto:admin@example.com" + - "discord://webhook_id/webhook_token" + responses: + '201': + description: Notification URLs added successfully + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationUrls' + '400': + description: Invalid input + + put: + tags: [Notifications] + summary: Replace notification URLs + description: Replace all notification URLs with the provided list (can be empty) + x-code-samples: + - lang: 'curl' + source: | + curl -X PUT "http://localhost:5000/api/v1/notifications" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "notification_urls": [ + "mailto:newadmin@example.com" + ] + }' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + data = { + 'notification_urls': [ + 'mailto:newadmin@example.com' + ] + } + response = requests.put('http://localhost:5000/api/v1/notifications', + headers=headers, json=data) + print(response.json()) + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationUrls' + responses: + '200': + description: Notification URLs replaced successfully + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationUrls' + '400': + description: Invalid input + + delete: + tags: [Notifications] + summary: Delete notification URLs + description: Delete one or more notification URLs from the configuration + x-code-samples: + - lang: 'curl' + source: | + curl -X DELETE "http://localhost:5000/api/v1/notifications" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "notification_urls": [ + "mailto:admin@example.com" + ] + }' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'application/json' + } + data = { + 'notification_urls': [ + 'mailto:admin@example.com' + ] + } + response = requests.delete('http://localhost:5000/api/v1/notifications', + headers=headers, json=data) + print(response.status_code) + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationUrls' + responses: + '204': + description: Notification URLs deleted successfully + '400': + description: No matching notification URLs found + + /search: + get: + tags: [Search] + summary: Search watches + description: Search watches by URL or title text + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/search?q=example.com" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + params = {'q': 'example.com'} + response = requests.get('http://localhost:5000/api/v1/search', + headers=headers, params=params) + print(response.json()) + parameters: + - name: q + in: query + required: true + description: Search query to match against watch URLs and titles + schema: + type: string + - name: tag + in: query + description: Tag name to limit results (name not UUID) + schema: + type: string + - name: partial + in: query + description: Allow partial matching of URL query + schema: + type: string + responses: + '200': + description: Search results + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResult' + example: + watches: + "095be615-a8ad-4c33-8e9c-c7612fbf6c9f": + uuid: "095be615-a8ad-4c33-8e9c-c7612fbf6c9f" + url: "http://example.com" + title: "Example Website Monitor" + tag: "550e8400-e29b-41d4-a716-446655440000" + tags: ["550e8400-e29b-41d4-a716-446655440000"] + paused: false + muted: false + + /import: + post: + tags: [Import] + summary: Import watch URLs + description: Import a list of URLs to monitor. Accepts line-separated URLs in request body. + x-code-samples: + - lang: 'curl' + source: | + curl -X POST "http://localhost:5000/api/v1/import" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: text/plain" \ + -d $'https://example.com\nhttps://example.org\nhttps://example.net' + - lang: 'Python' + source: | + import requests + + headers = { + 'x-api-key': 'YOUR_API_KEY', + 'Content-Type': 'text/plain' + } + urls = 'https://example.com\nhttps://example.org\nhttps://example.net' + response = requests.post('http://localhost:5000/api/v1/import', + headers=headers, data=urls) + print(response.json()) + parameters: + - name: tag_uuids + in: query + description: Tag UUID to apply to imported watches + schema: + type: string + - name: tag + in: query + description: Tag name to apply to imported watches + schema: + type: string + - name: proxy + in: query + description: Proxy key to use for imported watches + schema: + type: string + - name: dedupe + in: query + description: Remove duplicate URLs (default true) + schema: + type: boolean + default: true + requestBody: + required: true + content: + text/plain: + schema: + type: string + example: | + https://example.com + https://example.org + https://example.net + responses: + '200': + description: URLs imported successfully + content: + application/json: + schema: + type: array + items: + type: string + format: uuid + description: List of created watch UUIDs + '500': + description: Server error + + /systeminfo: + get: + tags: [System Information] + summary: Get system information + description: Return information about the current system state + x-code-samples: + - lang: 'curl' + source: | + curl -X GET "http://localhost:5000/api/v1/systeminfo" \ + -H "x-api-key: YOUR_API_KEY" + - lang: 'Python' + source: | + import requests + + headers = {'x-api-key': 'YOUR_API_KEY'} + response = requests.get('http://localhost:5000/api/v1/systeminfo', headers=headers) + print(response.json()) + responses: + '200': + description: System information + content: + application/json: + schema: + $ref: '#/components/schemas/SystemInfo' + example: + watch_count: 42 + tag_count: 5 + uptime: "2 days, 3:45:12" + version: "0.50.10" \ No newline at end of file diff --git a/docs/api_v1/index.html b/docs/api_v1/index.html index 9eba755f..29a1f953 100644 --- a/docs/api_v1/index.html +++ b/docs/api_v1/index.html @@ -1,2232 +1,923 @@ - + + - - - API Documentation - - - - + + ChangeDetection.io API + + + + + + -
-
- - - - -
- -
-
-

ChangeDetection.io, Web page monitoring and notifications API

-

REST API for managing Page watches, Group tags, and Notifications.

- -

changedetection.io can be driven by its built in simple API, in the examples below you will also find curl command line examples to help you.

+ +

ChangeDetection.io API (1.0.0)

Download OpenAPI specification:

ChangeDetection.io Web page monitoring and notifications API

REST API for managing Page watches, Group tags, and Notifications.

+

changedetection.io can be driven by its built in simple API, in the examples below you will also find curl command line examples to help you.

+

Where to find my API key?

The API key can be easily found under the SETTINGS then API tab of changedetection.io dashboard.
Simply click the API key to automatically copy it to your clipboard.

+

Where to find the API key

+

Connection URL

The API can be found at /api/v1/, so for example if you run changedetection.io locally on port 5000, then URL would be http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history.

+

If you are using the hosted/subscription version of changedetection.io, then the URL is based on your login URL, for example:
https://<your login url>/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history

+

Authentication

Almost all API requests require some authentication, this is provided as an API Key in the header of the HTTP request.

+

For example: x-api-key: YOUR_API_KEY

+

Watch Management

Core functionality for managing web page monitors. Create, retrieve, update, and delete individual watches. +Each watch represents a single URL being monitored for changes, with configurable settings for check intervals, +notification preferences, and content filtering options.

+

List all watches

Return concise list of available watches and basic info

+
Authorizations:
ApiKeyAuth
query Parameters
recheck_all
string
Value: "1"

Set to 1 to force recheck of all watches

+
tag
string

Tag name to filter results

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/watch" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
application/json
{
  • "095be615-a8ad-4c33-8e9c-c7612fbf6c9f": {
    },
  • "7c9e6b8d-f2a1-4e5c-9d3b-8a7f6e4c2d1a": {
    }
}

Create a new watch

Create a single watch. Requires at least 'url' to be set.

+
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
url
required
string <uri> <= 5000 characters

URL to monitor for changes

+
title
string <= 5000 characters

Custom title for the watch

+
tag
string <= 5000 characters

Tag UUID to associate with this watch

+
tags
Array of strings

Array of tag UUIDs

+
paused
boolean

Whether the watch is paused

+
muted
boolean

Whether notifications are muted

+
method
string
Enum: "GET" "POST" "DELETE" "PUT"

HTTP method to use

+
fetch_backend
string
Enum: "html_requests" "html_webdriver"

Backend to use for fetching content

+
object

HTTP headers to include in requests

+
body
string <= 5000 characters

HTTP request body

+
proxy
string <= 5000 characters

Proxy configuration

+
webdriver_delay
integer

Delay in seconds for webdriver

+
webdriver_js_execute_code
string <= 5000 characters

JavaScript code to execute

+
object

Time intervals between checks

+
notification_urls
Array of strings

Notification URLs for this watch

+
notification_title
string <= 5000 characters

Custom notification title

+
notification_body
string <= 5000 characters

Custom notification body

+
notification_format
string
Enum: "Text" "HTML" "Markdown"

Format for notifications

+
track_ldjson_price_data
boolean

Whether to track JSON-LD price data

+
Array of objects

Browser automation steps

+

Responses

Request samples

Content type
application/json
{
  • "title": "Example Site Monitor",
  • "time_between_check": {
    }
}

Get single watch

Retrieve watch information and set muted/paused status. Returns the FULL Watch JSON.

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Watch unique ID

+
query Parameters
recheck
string
Enum: "1" "true"

Recheck this watch

+
paused
string
Enum: "paused" "unpaused"

Set pause state

+
muted
string
Enum: "muted" "unmuted"

Set mute state

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
{
  • "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f",
  • "title": "string",
  • "tag": "string",
  • "tags": [
    ],
  • "paused": true,
  • "muted": true,
  • "method": "GET",
  • "fetch_backend": "html_requests",
  • "headers": {
    },
  • "body": "string",
  • "proxy": "string",
  • "webdriver_delay": 0,
  • "webdriver_js_execute_code": "string",
  • "time_between_check": {
    },
  • "notification_urls": [
    ],
  • "notification_title": "string",
  • "notification_body": "string",
  • "notification_format": "Text",
  • "track_ldjson_price_data": true,
  • "browser_steps": [
    ],
  • "last_checked": 0,
  • "last_changed": 0,
  • "last_error": "string"
}

Update watch

Update an existing watch using JSON. Accepts the same structure as returned in get single watch information.

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Watch unique ID

+
Request Body schema: application/json
required
url
required
string <uri> <= 5000 characters

URL to monitor for changes

+
title
string <= 5000 characters

Custom title for the watch

+
tag
string <= 5000 characters

Tag UUID to associate with this watch

+
tags
Array of strings

Array of tag UUIDs

+
paused
boolean

Whether the watch is paused

+
muted
boolean

Whether notifications are muted

+
method
string
Enum: "GET" "POST" "DELETE" "PUT"

HTTP method to use

+
fetch_backend
string
Enum: "html_requests" "html_webdriver"

Backend to use for fetching content

+
object

HTTP headers to include in requests

+
body
string <= 5000 characters

HTTP request body

+
proxy
string <= 5000 characters

Proxy configuration

+
webdriver_delay
integer

Delay in seconds for webdriver

+
webdriver_js_execute_code
string <= 5000 characters

JavaScript code to execute

+
object

Time intervals between checks

+
notification_urls
Array of strings

Notification URLs for this watch

+
notification_title
string <= 5000 characters

Custom notification title

+
notification_body
string <= 5000 characters

Custom notification body

+
notification_format
string
Enum: "Text" "HTML" "Markdown"

Format for notifications

+
track_ldjson_price_data
boolean

Whether to track JSON-LD price data

+
Array of objects

Browser automation steps

+

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "tag": "string",
  • "tags": [
    ],
  • "paused": true,
  • "muted": true,
  • "method": "GET",
  • "fetch_backend": "html_requests",
  • "headers": {
    },
  • "body": "string",
  • "proxy": "string",
  • "webdriver_delay": 0,
  • "webdriver_js_execute_code": "string",
  • "time_between_check": {
    },
  • "notification_urls": [
    ],
  • "notification_title": "string",
  • "notification_body": "string",
  • "notification_format": "Text",
  • "track_ldjson_price_data": true,
  • "browser_steps": [
    ]
}

Delete watch

Delete a watch and all related history

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Watch unique ID

+

Responses

Request samples

curl -X DELETE "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f" \
+  -H "x-api-key: YOUR_API_KEY"
+

Watch History

Access historical snapshots and change data for your watches. View the complete timeline of detected changes +and retrieve specific versions of monitored content for comparison and analysis.

+

Get watch history

Get a list of all historical snapshots available for a watch

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Watch unique ID

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
application/json
{
  • "1640995200": "/path/to/snapshot1.txt",
  • "1640998800": "/path/to/snapshot2.txt"
}

Snapshots

Retrieve individual snapshots of monitored content. Access both the processed change detection data and +the raw HTML content that was captured during monitoring checks.

+

Get single snapshot

Get single snapshot from watch. Use 'latest' for the most recent snapshot.

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Watch unique ID

+
required
integer or string

Snapshot timestamp or 'latest'

+
query Parameters
html
string
Value: "1"

Set to 1 to return the last HTML

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/history/latest" \
+  -H "x-api-key: YOUR_API_KEY"
+

Favicon

Retrieve favicon images associated with monitored web pages. These are used in the dashboard interface +to visually identify different watches in your monitoring list.

+

Get watch favicon

Get the favicon for a watch as displayed in the watch overview list.

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Watch unique ID

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/watch/095be615-a8ad-4c33-8e9c-c7612fbf6c9f/favicon" \
+  -H "x-api-key: YOUR_API_KEY" \
+  --output favicon.ico
+

Group / Tag Management

Organize your watches using tags and groups. Tags (also known as Groups) allow you to categorize monitors, set group-wide +notification preferences, and perform bulk operations like mass rechecking or status changes across +multiple related watches.

+

List all tags

Return list of available tags/groups

+
Authorizations:
ApiKeyAuth

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/tags" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
application/json
{
  • "550e8400-e29b-41d4-a716-446655440000": {
    },
  • "330e8400-e29b-41d4-a716-446655440001": {
    }
}

Get single tag

Retrieve tag information, set notification_muted status, recheck all in tag.

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Tag unique ID

+
query Parameters
muted
string
Enum: "muted" "unmuted"

Set mute state

+
recheck
string
Value: "true"

Queue all watches with this tag for recheck

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
{
  • "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f",
  • "title": "string",
  • "notification_urls": [
    ],
  • "notification_muted": true
}

Update tag

Update an existing tag using JSON

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Tag unique ID

+
Request Body schema: application/json
required
title
required
string <= 5000 characters

Tag title

+
notification_urls
Array of strings

Default notification URLs for watches with this tag

+
notification_muted
boolean

Whether notifications are muted for this tag

+

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "notification_urls": [
    ],
  • "notification_muted": true
}

Delete tag

Delete a tag/group and remove it from all watches

+
Authorizations:
ApiKeyAuth
path Parameters
uuid
required
string <uuid>

Tag unique ID

+

Responses

Request samples

curl -X DELETE "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \
+  -H "x-api-key: YOUR_API_KEY"
+

Create tag

Create a single tag/group

+
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
title
required
string <= 5000 characters

Tag title

+
notification_urls
Array of strings

Default notification URLs for watches with this tag

+
notification_muted
boolean

Whether notifications are muted for this tag

+

Responses

Request samples

Content type
application/json
{
  • "title": "Important Sites"
}

Notifications

Configure global notification endpoints that can be used across all your watches. Supports various +notification services including email, Discord, Slack, webhooks, and many other popular platforms. +These settings serve as defaults that can be overridden at the individual watch or tag level.

+

Get notification URLs

Return the notification URL list from the configuration

+
Authorizations:
ApiKeyAuth

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/notifications" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
application/json
{}

Add notification URLs

Add one or more notification URLs to the configuration

+
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
notification_urls
required
Array of strings <uri> [ items <uri > ]

List of notification URLs

+

Responses

Request samples

Content type
application/json
{
  • "notification_urls": [
    ]
}

Response samples

Content type
application/json
{}

Replace notification URLs

Replace all notification URLs with the provided list (can be empty)

+
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
notification_urls
required
Array of strings <uri> [ items <uri > ]

List of notification URLs

+

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{}

Delete notification URLs

Delete one or more notification URLs from the configuration

+
Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
notification_urls
required
Array of strings <uri> [ items <uri > ]

List of notification URLs

+

Responses

Request samples

Content type
application/json
{}

Search

Search and filter your watches by URL patterns, titles, or tags. Useful for quickly finding specific +monitors in large collections or identifying watches that match certain criteria.

+

Search watches

Search watches by URL or title text

+
Authorizations:
ApiKeyAuth
query Parameters
q
required
string

Search query to match against watch URLs and titles

+
tag
string

Tag name to limit results (name not UUID)

+
partial
string

Allow partial matching of URL query

+

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/search?q=example.com" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
application/json
{
  • "watches": {
    }
}

Import

Bulk import multiple URLs for monitoring. Accepts plain text lists of URLs and can automatically +apply tags, proxy settings, and other configurations to all imported watches simultaneously.

+

Import watch URLs

Import a list of URLs to monitor. Accepts line-separated URLs in request body.

+
Authorizations:
ApiKeyAuth
query Parameters
tag_uuids
string

Tag UUID to apply to imported watches

+
tag
string

Tag name to apply to imported watches

+
proxy
string

Proxy key to use for imported watches

+
dedupe
boolean
Default: true

Remove duplicate URLs (default true)

+
Request Body schema: text/plain
required
string

Responses

Request samples

Content type
text/plain
https://example.com
+https://example.org
+https://example.net
+

Response samples

Content type
application/json
[
  • "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]

System Information

Retrieve system status and statistics about your changedetection.io instance, including total watch +counts, uptime information, and version details.

+

Get system information

Return information about the current system state

+
Authorizations:
ApiKeyAuth

Responses

Request samples

curl -X GET "http://localhost:5000/api/v1/systeminfo" \
+  -H "x-api-key: YOUR_API_KEY"
+

Response samples

Content type
application/json
{
  • "watch_count": 42,
  • "tag_count": 5,
  • "uptime": "2 days, 3:45:12",
  • "version": "0.50.10"
}
+ - - - - - + - \ No newline at end of file + + diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..dcad00aa --- /dev/null +++ b/docs/package.json @@ -0,0 +1,12 @@ +{ + "name": "changedetection-api-docs", + "version": "1.0.0", + "description": "API documentation generation for changedetection.io", + "private": true, + "scripts": { + "build-docs": "redocly build-docs api-spec.yaml --output api_v1/index.html" + }, + "devDependencies": { + "@redocly/cli": "^1.34.5" + } +} \ No newline at end of file diff --git a/docs/python-apidoc/apidoc.py b/docs/python-apidoc/apidoc.py deleted file mode 100755 index d55ba272..00000000 --- a/docs/python-apidoc/apidoc.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/usr/bin/env python3 -""" -Python API Documentation Generator -Parses @api comments from Python files and generates Bootstrap HTML docs -""" - -import re -import os -import json -import argparse -from pathlib import Path -from dataclasses import dataclass, field -from typing import List, Dict, Any -from jinja2 import Template - -@dataclass -class ApiEndpoint: - method: str = "" - url: str = "" - title: str = "" - name: str = "" - group: str = "General" - group_order: int = 999 # Default to high number (low priority) - group_doc_order: int = 999 # Default to high number (low priority) for sidebar ordering - description: str = "" - params: List[Dict[str, Any]] = field(default_factory=list) - query: List[Dict[str, Any]] = field(default_factory=list) - success: List[Dict[str, Any]] = field(default_factory=list) - error: List[Dict[str, Any]] = field(default_factory=list) - example: str = "" - example_request: str = "" - example_response: str = "" - -def prettify_json(text: str) -> str: - """Attempt to prettify JSON content in the text""" - if not text or not text.strip(): - return text - - # First, try to parse the entire text as JSON - stripped_text = text.strip() - try: - json_obj = json.loads(stripped_text) - return json.dumps(json_obj, indent=2, ensure_ascii=False) - except (json.JSONDecodeError, ValueError): - pass - - # If that fails, try to find JSON blocks within the text - lines = text.split('\n') - prettified_lines = [] - i = 0 - - while i < len(lines): - line = lines[i] - stripped_line = line.strip() - - # Look for the start of a JSON object or array - if stripped_line.startswith('{') or stripped_line.startswith('['): - # Try to collect a complete JSON block - json_lines = [stripped_line] - brace_count = stripped_line.count('{') - stripped_line.count('}') - bracket_count = stripped_line.count('[') - stripped_line.count(']') - - j = i + 1 - while j < len(lines) and (brace_count > 0 or bracket_count > 0): - next_line = lines[j].strip() - json_lines.append(next_line) - brace_count += next_line.count('{') - next_line.count('}') - bracket_count += next_line.count('[') - next_line.count(']') - j += 1 - - # Try to parse and prettify the collected JSON block - json_block = '\n'.join(json_lines) - try: - json_obj = json.loads(json_block) - prettified = json.dumps(json_obj, indent=2, ensure_ascii=False) - prettified_lines.append(prettified) - i = j # Skip the lines we just processed - continue - except (json.JSONDecodeError, ValueError): - # If parsing failed, just add the original line - prettified_lines.append(line) - else: - prettified_lines.append(line) - - i += 1 - - return '\n'.join(prettified_lines) - -class ApiDocParser: - def __init__(self): - self.patterns = { - 'api': re.compile(r'@api\s*\{(\w+)\}\s*([^\s]+)\s*(.*)'), - 'apiName': re.compile(r'@apiName\s+(.*)'), - 'apiGroup': re.compile(r'@apiGroup\s+(.*)'), - 'apiGroupOrder': re.compile(r'@apiGroupOrder\s+(\d+)'), - 'apiGroupDocOrder': re.compile(r'@apiGroupDocOrder\s+(\d+)'), - 'apiDescription': re.compile(r'@apiDescription\s+(.*)'), - 'apiParam': re.compile(r'@apiParam\s*\{([^}]+)\}\s*(\[?[\w.:]+\]?)\s*(.*)'), - 'apiQuery': re.compile(r'@apiQuery\s*\{([^}]+)\}\s*(\[?[\w.:]+\]?)\s*(.*)'), - 'apiSuccess': re.compile(r'@apiSuccess\s*\((\d+)\)\s*\{([^}]+)\}\s*(\w+)?\s*(.*)'), - 'apiError': re.compile(r'@apiError\s*\((\d+)\)\s*\{([^}]+)\}\s*(.*)'), - 'apiExample': re.compile(r'@apiExample\s*\{([^}]+)\}\s*(.*)'), - 'apiExampleRequest': re.compile(r'@apiExampleRequest\s*\{([^}]+)\}\s*(.*)'), - 'apiExampleResponse': re.compile(r'@apiExampleResponse\s*\{([^}]+)\}\s*(.*)'), - } - - def parse_file(self, file_path: Path) -> List[ApiEndpoint]: - """Parse a single Python file for @api comments""" - try: - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - except Exception as e: - print(f"Error reading {file_path}: {e}") - return [] - - endpoints = [] - current_endpoint = None - in_multiline_example = False - in_multiline_request = False - in_multiline_response = False - example_lines = [] - request_lines = [] - response_lines = [] - - for line in content.split('\n'): - line_stripped = line.strip() - - # Handle multiline examples, requests, and responses - if in_multiline_example or in_multiline_request or in_multiline_response: - # Check if this line starts a new example type or exits multiline mode - should_exit_multiline = False - - if line_stripped.startswith('@apiExampleRequest'): - # Finalize current multiline block and start request - should_exit_multiline = True - elif line_stripped.startswith('@apiExampleResponse'): - # Finalize current multiline block and start response - should_exit_multiline = True - elif line_stripped.startswith('@apiExample'): - # Finalize current multiline block and start example - should_exit_multiline = True - elif line_stripped.startswith('@api') and not any(x in line_stripped for x in ['@apiExample', '@apiExampleRequest', '@apiExampleResponse']): - # Exit multiline mode for any other @api directive - should_exit_multiline = True - - if should_exit_multiline: - # Finalize any active multiline blocks - if in_multiline_example and current_endpoint and example_lines: - current_endpoint.example = '\n'.join(example_lines) - if in_multiline_request and current_endpoint and request_lines: - current_endpoint.example_request = '\n'.join(request_lines) - if in_multiline_response and current_endpoint and response_lines: - raw_response = '\n'.join(response_lines) - current_endpoint.example_response = prettify_json(raw_response) - - # Reset all multiline states - in_multiline_example = False - in_multiline_request = False - in_multiline_response = False - example_lines = [] - request_lines = [] - response_lines = [] - - # If this is still an example directive, continue processing it - if not (line_stripped.startswith('@apiExample') or line_stripped.startswith('@apiExampleRequest') or line_stripped.startswith('@apiExampleResponse')): - # This is a different @api directive, let it be processed normally - pass - # If it's an example directive, it will be processed below - else: - # For multiline blocks, preserve the content more liberally - # Remove leading comment markers but preserve structure - clean_line = re.sub(r'^\s*[#*/]*\s?', '', line) - # Add the line if it has content or if it's an empty line (for formatting) - if clean_line or not line_stripped: - if in_multiline_example: - example_lines.append(clean_line) - elif in_multiline_request: - request_lines.append(clean_line) - elif in_multiline_response: - response_lines.append(clean_line) - continue - - # Skip non-comment lines - if not any(marker in line_stripped for marker in ['@api', '#', '*', '//']): - continue - - # Extract @api patterns - for pattern_name, pattern in self.patterns.items(): - match = pattern.search(line_stripped) - if match: - if pattern_name == 'api': - # Start new endpoint - if current_endpoint: - endpoints.append(current_endpoint) - current_endpoint = ApiEndpoint() - current_endpoint.method = match.group(1).lower() - current_endpoint.url = match.group(2) - current_endpoint.title = match.group(3).strip() - - elif current_endpoint: - if pattern_name == 'apiName': - current_endpoint.name = match.group(1) - elif pattern_name == 'apiGroup': - current_endpoint.group = match.group(1) - elif pattern_name == 'apiGroupOrder': - current_endpoint.group_order = int(match.group(1)) - elif pattern_name == 'apiGroupDocOrder': - current_endpoint.group_doc_order = int(match.group(1)) - elif pattern_name == 'apiDescription': - current_endpoint.description = match.group(1) - elif pattern_name == 'apiParam': - param_type = match.group(1) - param_name = match.group(2).strip('[]') - param_desc = match.group(3) - optional = '[' in match.group(2) - current_endpoint.params.append({ - 'type': param_type, - 'name': param_name, - 'description': param_desc, - 'optional': optional - }) - elif pattern_name == 'apiQuery': - param_type = match.group(1) - param_name = match.group(2).strip('[]') - param_desc = match.group(3) - optional = '[' in match.group(2) - current_endpoint.query.append({ - 'type': param_type, - 'name': param_name, - 'description': param_desc, - 'optional': optional - }) - elif pattern_name == 'apiSuccess': - status_code = match.group(1) - response_type = match.group(2) - response_name = match.group(3) or 'response' - response_desc = match.group(4) - current_endpoint.success.append({ - 'status': status_code, - 'type': response_type, - 'name': response_name, - 'description': response_desc - }) - elif pattern_name == 'apiError': - status_code = match.group(1) - error_type = match.group(2) - error_desc = match.group(3) - current_endpoint.error.append({ - 'status': status_code, - 'type': error_type, - 'description': error_desc - }) - elif pattern_name == 'apiExample': - in_multiline_example = True - # Skip the "{curl} Example usage:" header line - example_lines = [] - elif pattern_name == 'apiExampleRequest': - in_multiline_request = True - # Skip the "{curl} Request:" header line - request_lines = [] - elif pattern_name == 'apiExampleResponse': - in_multiline_response = True - # Skip the "{json} Response:" header line - response_lines = [] - break - - # Don't forget the last endpoint - if current_endpoint: - if in_multiline_example and example_lines: - current_endpoint.example = '\n'.join(example_lines) - if in_multiline_request and request_lines: - current_endpoint.example_request = '\n'.join(request_lines) - if in_multiline_response and response_lines: - raw_response = '\n'.join(response_lines) - current_endpoint.example_response = prettify_json(raw_response) - endpoints.append(current_endpoint) - - return endpoints - - def parse_directory(self, directory: Path) -> List[ApiEndpoint]: - """Parse all Python files in a directory""" - all_endpoints = [] - - for py_file in directory.rglob('*.py'): - endpoints = self.parse_file(py_file) - all_endpoints.extend(endpoints) - - return all_endpoints - -def generate_html(endpoints: List[ApiEndpoint], output_file: Path, template_file: Path): - """Generate HTML documentation using Jinja2 template""" - - # Group endpoints by group and collect group orders - grouped_endpoints = {} - group_orders = {} - group_doc_orders = {} - - for endpoint in endpoints: - group = endpoint.group - if group not in grouped_endpoints: - grouped_endpoints[group] = [] - group_orders[group] = endpoint.group_order - group_doc_orders[group] = endpoint.group_doc_order - grouped_endpoints[group].append(endpoint) - - # Use the lowest order value for the group (in case of multiple definitions) - group_orders[group] = min(group_orders[group], endpoint.group_order) - group_doc_orders[group] = min(group_doc_orders[group], endpoint.group_doc_order) - - # Sort groups by doc order for sidebar (0 = highest priority), then by content order, then alphabetically - sorted_groups = sorted(grouped_endpoints.items(), key=lambda x: (group_doc_orders[x[0]], group_orders[x[0]], x[0])) - - # Convert back to ordered dict and sort endpoints within each group - grouped_endpoints = {} - for group, endpoints_list in sorted_groups: - endpoints_list.sort(key=lambda x: (x.name, x.url)) - grouped_endpoints[group] = endpoints_list - - # Load template - with open(template_file, 'r', encoding='utf-8') as f: - template_content = f.read() - - # Load introduction content - introduction_file = template_file.parent / 'introduction.html' - introduction_content = "" - if introduction_file.exists(): - with open(introduction_file, 'r', encoding='utf-8') as f: - introduction_content = f.read() - - # Load sidebar header content - sidebar_header_file = template_file.parent / 'sidebar-header.html' - sidebar_header_content = "

API Documentation

" # Default fallback - if sidebar_header_file.exists(): - with open(sidebar_header_file, 'r', encoding='utf-8') as f: - sidebar_header_content = f.read() - - template = Template(template_content) - html_content = template.render( - grouped_endpoints=grouped_endpoints, - introduction_content=introduction_content, - sidebar_header_content=sidebar_header_content - ) - - with open(output_file, 'w', encoding='utf-8') as f: - f.write(html_content) - -def main(): - parser = argparse.ArgumentParser(description='Generate API documentation from Python source files') - parser.add_argument('-i', '--input', default='.', - help='Input directory to scan for Python files (default: current directory)') - parser.add_argument('-o', '--output', default='api_docs.html', - help='Output HTML file (default: api_docs.html)') - parser.add_argument('-t', '--template', default='template.html', - help='Template HTML file (default: template.html)') - - args = parser.parse_args() - - input_path = Path(args.input) - output_path = Path(args.output) - template_path = Path(args.template) - - # Make template path relative to script location if not absolute - if not template_path.is_absolute(): - template_path = Path(__file__).parent / template_path - - if not input_path.exists(): - print(f"Error: Input directory '{input_path}' does not exist") - return 1 - - if not template_path.exists(): - print(f"Error: Template file '{template_path}' does not exist") - return 1 - - print(f"Scanning {input_path} for @api comments...") - - doc_parser = ApiDocParser() - endpoints = doc_parser.parse_directory(input_path) - - if not endpoints: - print("No API endpoints found!") - return 1 - - print(f"Found {len(endpoints)} API endpoints") - - # Create output directory if needed - output_path.parent.mkdir(parents=True, exist_ok=True) - - print(f"Generating HTML documentation to {output_path}...") - generate_html(endpoints, output_path, template_path) - - print("Documentation generated successfully!") - print(f"Open {output_path.resolve()} in your browser to view the docs") - - return 0 - -if __name__ == '__main__': - exit(main()) \ No newline at end of file diff --git a/docs/python-apidoc/changedetection_api_docs.html b/docs/python-apidoc/changedetection_api_docs.html deleted file mode 100644 index cbf6ee6e..00000000 --- a/docs/python-apidoc/changedetection_api_docs.html +++ /dev/null @@ -1,2182 +0,0 @@ - - - - - - Changedetection.io - API Documentation - - - - - - -
-
- - - - -
- -
-
-

ChangeDetection.io, Web page monitoring and notifications API

-

REST API for managing Page watches, Group tags, and Notifications.

- -

changedetection.io can be driven by its built in simple API, in the examples below you will also find curl command line examples to help you.

- -

-

Where to find my API key?
-The API key can be easily found under the SETTINGS then API tab of changedetection.io dashboard.
-Simply click the API key to automatically copy it to your clipboard.

-Where to find the API key -

- -

-

Connection URL
- The API can be found at /api/v1/, so for example if you run changedetection.io locally on port 5000, then URL would be - http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history.

- If you are using the hosted/subscription version of changedetection.io, then the URL is based on your login URL, for example.
-https://<your login url>/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history -

-

-

Authentication
-Almost all API requests require some authentication, this is provided as an API Key in the header of the HTTP request.

-For example; -
x-api-key: YOUR_API_KEY
-

-
-
- - -

Watch

- - -
-
-
-

- post - /api/v1/watch -

-
Create
- -

Requires atleast `url` set, can accept the same structure as get single watch information to create.

- -
-
- - - - - - -
Success Responses
- -
- 200 - String - OK - Was created -
- -
- 500 - String - ERR - Some other error -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"url": "https://my-nice.com" , "tag": "nice list"}'
-
- - -
- -
-
-
-

- delete - /api/v1/watch/:uuid -

-
Delete
- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
uuiduuidRequiredWatch unique ID.
-
- - - - - -
Success Responses
- -
- 200 - String - OK - Was deleted -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - -
- -
-
-
-

- put - /api/v1/watch/:uuid -

-
Update a watch
- -

Updates an existing watch using JSON, accepts the same structure as returned in get single watch information

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
uuiduuidRequiredWatch unique ID.
-
- - - - - -
Success Responses
- -
- 200 - String - OK - Was updated -
- -
- 500 - String - ERR - Some other error -
- - - - - - -
Example
- - - - - - -
- -
Update (PUT)
-curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X PUT -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"url": "https://my-nice.com" , "tag": "new list"}'
-
-
- - -
- -
-
-
-

- get - /api/v1/watch/:uuid -

-
Watch
- -

Retrieve watch information and set muted/paused status

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
uuiduuidRequiredWatch unique ID.
-
- - - -
Query Parameters
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
recheckBooleanOptionalRecheck this watch `recheck=1`
pausedStringOptional=`paused` or =`unpaused` , Sets the PAUSED state
mutedStringOptional=`muted` or =`unmuted` , Sets the MUTE NOTIFICATIONS state
-
- - - -
Success Responses
- -
- 200 - String - OK - When paused/muted/recheck operation OR full JSON object of the watch -
- -
- 200 - JSON - WatchJSON - JSON Full JSON object of the watch -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091  -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-curl "http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091?muted=unmuted"  -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-curl "http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091?paused=unpaused"  -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - -
- - -

Watch History

- - -
-
-
-

- get - /api/v1/watch/<string:uuid>/history -

-
Get list of available stored snapshots for watch
- -

Requires `uuid`, returns list

- -
-
- - - - - - -
Success Responses
- -
- 200 - String - OK - -
- -
- 404 - String - ERR - Not found -
- - - - - - -
Example
- - - Request -
- -
curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json"
-
- - - - Response -
- -
{
-  "1676649279": "/tmp/data/6a4b7d5c-fee4-4616-9f43-4ac97046b595/cb7e9be8258368262246910e6a2a4c30.txt",
-  "1677092785": "/tmp/data/6a4b7d5c-fee4-4616-9f43-4ac97046b595/e20db368d6fc633e34f559ff67bb4044.txt",
-  "1677103794": "/tmp/data/6a4b7d5c-fee4-4616-9f43-4ac97046b595/02efdd37dacdae96554a8cc85dc9c945.txt"
-}
-
- - - - -
- - -

Snapshots

- - -
-
-
-

- get - /api/v1/watch/<string:uuid>/history/<int:timestamp> -

-
Get single snapshot content
- -

Requires watch `uuid` and `timestamp`. `timestamp` of "`latest`" for latest available snapshot, or use the list returned here

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
htmlStringOptionalOptional Set to =1 to return the last HTML (only stores last 2 snapshots, use `latest` as timestamp)
-
- - - - - -
Success Responses
- -
- 200 - String - OK - -
- -
- 404 - String - ERR - Not found -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history/1677092977 -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json"
-
- - -
- - -

Favicon

- - -
-
-
-

- get - /api/v1/watch/<string:uuid>/favicon -

-
Get latest Favicon
- -

Requires watch `uuid`

- -
-
- - - - - - -
Success Responses
- -
- 200 - String - OK - -
- -
- 404 - String - ERR - Not found -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/favicon -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - -
- - -

Watch Management

- - -
-
-
-

- get - /api/v1/watch -

-
ListWatches
- -

Return concise list of available watches and some very basic info

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
recheck_allStringOptionalOptional Set to =1 to force recheck of all watches
tagStringOptionalOptional name of tag to limit results
-
- - - - - -
Success Responses
- -
- 200 - String - OK - JSON dict -
- - - - - - -
Example
- - - Request -
- -
curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - - - Response -
- -
{
-  "6a4b7d5c-fee4-4616-9f43-4ac97046b595": {
-    "last_changed": 1677103794,
-    "last_checked": 1677103794,
-    "last_error": false,
-    "title": "",
-    "url": "http://www.quotationspage.com/random.php"
-  },
-  "e6f5fd5c-dbfe-468b-b8f3-f9d6ff5ad69b": {
-    "last_changed": 0,
-    "last_checked": 1676662819,
-    "last_error": false,
-    "title": "QuickLook",
-    "url": "https://github.com/QL-Win/QuickLook/tags"
-  }
-}
-
- - - - -
- - -

Group / Tag

- - -
-
-
-

- post - /api/v1/watch -

-
Create
- -
-
- - - - - - -
Success Responses
- -
- 200 - String - OK - Was created -
- -
- 500 - String - ERR - Some other error -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"name": "Work related"}'
-
- - -
- -
-
-
-

- delete - /api/v1/tag/:uuid -

-
DeleteTag
- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
uuiduuidRequiredTag unique ID.
-
- - - - - -
Success Responses
- -
- 200 - String - OK - Was deleted -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - -
- -
-
-
-

- get - /api/v1/tag/:uuid -

-
Tag
- -

Retrieve tag information, set notification_muted status, recheck all in tag.

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
uuiduuidRequiredTag unique ID.
-
- - - -
Query Parameters
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
mutedStringOptional=`muted` or =`unmuted` , Sets the MUTE NOTIFICATIONS state
recheckStringOptional= True, Queue all watches with this tag for recheck
-
- - - -
Success Responses
- -
- 200 - String - OK - When muted operation OR full JSON object of the tag -
- -
- 200 - JSON - TagJSON - JSON Full JSON object of the tag -
- - - - - - -
- -
-
-
-

- put - /api/v1/tag/:uuid -

-
UpdateTag
- -

Updates an existing tag using JSON

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
uuiduuidRequiredTag unique ID.
-
- - - - - -
Success Responses
- -
- 200 - String - OK - Was updated -
- -
- 500 - String - ERR - Some other error -
- - - - - - -
Example
- - - Request -
- -
curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X PUT -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"title": "New Tag Title"}'
-
- - - - Response -
- -
"OK"
-
- - - - -
- - -

Group / Tag Management

- - -
-
-
-

- get - /api/v1/tags -

-
ListTags
- -

Return list of available tags

- -
-
- - - - - - -
Success Responses
- -
- 200 - String - OK - JSON dict -
- - - - - - -
Example
- - - Request -
- -
curl http://localhost:5000/api/v1/tags -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - - - Response -
- -
{
-  "cc0cfffa-f449-477b-83ea-0caafd1dc091": {
-    "title": "Tech News",
-    "notification_muted": false,
-    "date_created": 1677103794
-  },
-  "e6f5fd5c-dbfe-468b-b8f3-f9d6ff5ad69b": {
-    "title": "Shopping",
-    "notification_muted": true,
-    "date_created": 1676662819
-  }
-}
-
- - - - -
- - -

Import

- - -
-
-
-

- post - /api/v1/import -

-
Import
- -

Accepts a line-feed separated list of URLs to import, additionally with ?tag_uuids=(tag id), ?tag=(name), ?proxy={key}, ?dedupe=true (default true) one URL per line.

- -
-
- - - - - - -
Success Responses
- -
- 200 - List - OK - List of watch UUIDs added -
- -
- 500 - String - ERR - Some other error -
- - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/import --data-binary @list-of-sites.txt -H"x-api-key:8a111a21bc2f8f1dd9b9353bbd46049a"
-
- - -
- - -

Notifications

- - -
-
-
-

- post - /api/v1/notifications -

-
CreateBatch
- -

Add one or more notification URLs from the configuration

- -
-
- - - - - - -
Success Responses
- -
- 201 - Object[] - notification_urls - List of added notification URLs -
- - - - -
Error Responses
- -
- 400 - String - Invalid input -
- - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/notifications/batch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"notification_urls": ["url1", "url2"]}'
-
- - -
- -
-
-
-

- delete - /api/v1/notifications -

-
Delete
- -

Deletes one or more notification URLs from the configuration

- -
-
- - -
Parameters
-
- - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
notification_urlsString[]RequiredThe notification URLs to delete.
-
- - - - - -
Success Responses
- -
- 204 - String - OK - Deleted -
- - - - -
Error Responses
- -
- 400 - String - No matching notification URLs found. -
- - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/notifications -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"notification_urls": ["url1", "url2"]}'
-
- - -
- -
-
-
-

- get - /api/v1/notifications -

-
Get
- -

Return the Notification URL List from the configuration

- -
-
- - - - - - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/notifications -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-HTTP/1.0 200
-{
-'notification_urls': ["notification-urls-list"]
-}
-
- - -
- -
-
-
-

- put - /api/v1/notifications -

-
Replace
- -

Replace all notification URLs with the provided list (can be empty)

- -
-
- - - - - - -
Success Responses
- -
- 200 - Object[] - notification_urls - List of current notification URLs -
- - - - -
Error Responses
- -
- 400 - String - Invalid input -
- - - - -
Example
- - - - - - -
- -
curl -X PUT http://localhost:5000/api/v1/notifications -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"notification_urls": ["url1", "url2"]}'
-
- - -
- - - - - -
-
-
-

- get - /api/v1/search -

-
Search
- -

Search watches by URL or title text

- -
-
- - - - -
Query Parameters
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeRequiredDescription
qStringRequiredSearch query to match against watch URLs and titles
tagStringOptionalOptional name of tag to limit results (name not UUID)
partialStringOptionalAllow partial matching of URL query
-
- - - -
Success Responses
- -
- 200 - Object - JSON - Object containing matched watches -
- - - - - - -
Example
- - - - - - -
- -
curl "http://localhost:5000/api/v1/search?q=https://example.com/page1" -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-curl "http://localhost:5000/api/v1/search?q=https://example.com/page1?tag=Favourites" -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-curl "http://localhost:5000/api/v1/search?q=https://example.com?partial=true" -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-
- - -
- - -

System Information

- - -
-
-
-

- get - /api/v1/systeminfo -

-
Get Info
- -

Return some info about the current system state

- -
-
- - - - - - - - - - -
Example
- - - - - - -
- -
curl http://localhost:5000/api/v1/systeminfo -H"x-api-key:813031b16330fe25e3780cf0325daa45"
-HTTP/1.0 200
-{
-'queue_size': 10 ,
-'overdue_watches': ["watch-uuid-list"],
-'uptime': 38344.55,
-'watch_count': 800,
-'version': "0.40.1"
-}
-
- - -
- - -
-
-
- - - - - - - - - \ No newline at end of file diff --git a/docs/python-apidoc/introduction.html b/docs/python-apidoc/introduction.html deleted file mode 100644 index 7fd66322..00000000 --- a/docs/python-apidoc/introduction.html +++ /dev/null @@ -1,27 +0,0 @@ -
-

ChangeDetection.io, Web page monitoring and notifications API

-

REST API for managing Page watches, Group tags, and Notifications.

- -

changedetection.io can be driven by its built in simple API, in the examples below you will also find curl command line examples to help you.

- -

-

Where to find my API key?
-The API key can be easily found under the SETTINGS then API tab of changedetection.io dashboard.
-Simply click the API key to automatically copy it to your clipboard.

-Where to find the API key -

- -

-

Connection URL
- The API can be found at /api/v1/, so for example if you run changedetection.io locally on port 5000, then URL would be - http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history.

- If you are using the hosted/subscription version of changedetection.io, then the URL is based on your login URL, for example.
-https://<your login url>/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/history -

-

-

Authentication
-Almost all API requests require some authentication, this is provided as an API Key in the header of the HTTP request.

-For example; -
x-api-key: YOUR_API_KEY
-

-
\ No newline at end of file diff --git a/docs/python-apidoc/sidebar-header.html b/docs/python-apidoc/sidebar-header.html deleted file mode 100644 index 9390742d..00000000 --- a/docs/python-apidoc/sidebar-header.html +++ /dev/null @@ -1 +0,0 @@ -

API Documentation

\ No newline at end of file diff --git a/docs/python-apidoc/template.html b/docs/python-apidoc/template.html deleted file mode 100644 index 1ccc3a0d..00000000 --- a/docs/python-apidoc/template.html +++ /dev/null @@ -1,506 +0,0 @@ - - - - - - API Documentation - - - - - - -
-
- - - - -
- {% if introduction_content %} -
- {{ introduction_content|safe }} -
- {% endif %} - {% for group, endpoints in grouped_endpoints.items() %} -

{{ group }}

- - {% for endpoint in endpoints %} -
-
-
-

- {{ endpoint.method }} - {{ endpoint.url|e }} -

-
{{ endpoint.name or endpoint.title }}
- {% if endpoint.description %} -

{{ endpoint.description|safe }}

- {% endif %} -
-
- - {% if endpoint.params %} -
Parameters
-
- - - - - - - - - - - {% for param in endpoint.params %} - - - - - - - {% endfor %} - -
NameTypeRequiredDescription
{{ param.name }}{{ param.type }}{% if param.optional %}Optional{% else %}Required{% endif %}{{ param.description }}
-
- {% endif %} - - {% if endpoint.query %} -
Query Parameters
-
- - - - - - - - - - - {% for param in endpoint.query %} - - - - - - - {% endfor %} - -
NameTypeRequiredDescription
{{ param.name }}{{ param.type }}{% if param.optional %}Optional{% else %}Required{% endif %}{{ param.description }}
-
- {% endif %} - - {% if endpoint.success %} -
Success Responses
- {% for success in endpoint.success %} -
- {{ success.status }} - {{ success.type }} - {{ success.name }} - {{ success.description }} -
- {% endfor %} - {% endif %} - - {% if endpoint.error %} -
Error Responses
- {% for error in endpoint.error %} -
- {{ error.status }} - {{ error.type }} - {{ error.description }} -
- {% endfor %} - {% endif %} - - {% if endpoint.example or endpoint.example_request or endpoint.example_response %} -
Example
- - {% if endpoint.example_request %} - Request -
- -
{{ endpoint.example_request }}
-
- {% endif %} - - {% if endpoint.example_response %} - Response -
- -
{{ endpoint.example_response }}
-
- {% endif %} - - {% if endpoint.example and not endpoint.example_request and not endpoint.example_response %} -
- -
{{ endpoint.example }}
-
- {% endif %} - {% endif %} -
- {% endfor %} - {% endfor %} -
-
-
- - - - - - - - - \ No newline at end of file