Monitor terhos
Berikan CrawlForge satu set halaman dan jadual cron. Ia mengambil dan membandingkannya pada penjadualnya sendiri, merekodkan setiap pemeriksaan, dan memberitahu anda apa yang berubah melalui e-mel atau webhook bertandatangan. Tiada yuran setiap monitor: setiap pemeriksaan mengecaj harga track_changes bagi setiap sasaran yang dibandingkan.
Gambaran keseluruhan
Monitor ialah satu nama, sehingga 20 sasaran (satu url serta selector CSS pilihan), jadual cron lima medan dengan zon waktu IANA, dan tempat untuk menghantar keputusan. Penjadual CrawlForge berjalan setiap 5 minit dan memulakan sendiri setiap pemeriksaan yang tiba masanya, jadi tiada apa-apa perlu berjalan di pihak anda.
Pemeriksaan pertama sesuatu sasaran menangkap garis dasar dan melaporkannya sebagai new. Setiap pemeriksaan seterusnya membandingkan dengan pemeriksaan sebelumnya dan menggulung garis dasar ke hadapan, jadi setiap pemeriksaan melaporkan perubahan sejak yang sebelumnya. Garis dasar kekal sehingga monitor dipadamkan; pemeriksaan dibersihkan selepas retention_days.
Setiap titik akhir menerima pengepala X-API-Key yang sama seperti alat (atau Authorization: Bearer cf_…). Panggilan pengurusan tidak mengecaj apa-apa dan berfungsi pada sifar credits. Respons menggunakan sampul alat, { success, data }, dan ralat membawa error.code dan error.message.
Titik akhir
| Kaedah | Laluan | Apa yang dilakukannya |
|---|---|---|
| POST | /api/v1/monitors | Cipta monitor. Memulangkan 201 dengan monitor itu, termasuk webhook_secret-nya. |
| GET | /api/v1/monitors | Senaraikan monitor, setiap satu dengan pemeriksaan terbarunya sebagai last_check. Parameter pertanyaan limit dan cursor; badan membawa next_cursor. Tidak pernah memulangkan webhook_secret. |
| GET | /api/v1/monitors/{id} | Satu monitor, termasuk webhook_secret. |
| PATCH | /api/v1/monitors/{id} | Kemas kini mana-mana subset medan penciptaan. Menetapkan status kepada paused menetapkan next_run_at kepada null. |
| DELETE | /api/v1/monitors/{id} | Padam monitor. Memulangkan { id, deleted: true }. |
| POST | /api/v1/monitors/{id}/run | Jalankan satu pemeriksaan sekarang, secara sebaris, dan pulangkannya dengan pages. 409 MONITOR_RUNNING semasa satu pemeriksaan sudah berjalan. |
| GET | /api/v1/monitors/{id}/checks | Sejarah pemeriksaan tanpa pages. Parameter pertanyaan limit dan cursor. |
| GET | /api/v1/monitors/{id}/checks/{check_id} | Satu pemeriksaan dengan pages dan deliveries webhook. |
Cipta monitor
Hanya name dan targets yang diperlukan. Nilai lalai memberi anda pemeriksaan setiap jam dalam UTC dengan sejarah 30 hari.
curl -X POST https://crawlforge.dev/api/v1/monitors \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "competitor pricing",
"targets": [{ "url": "https://competitor.com/pricing", "selector": ".pricing-table" }],
"schedule_cron": "0 * * * *",
"webhook_url": "https://example.com/hooks/crawlforge"
}'Setiap medan yang diterima oleh badan. PATCH menerima mana-mana subset medan yang sama.
| Medan | Jenis | Lalai | Penerangan |
|---|---|---|---|
name | string | diperlukan | 1 hingga 80 aksara. |
targets | array | diperlukan | 1 hingga 20 objek { url, selector? }. URL mestilah alamat http(s) awam; alamat peribadi atau tempatan ditolak dengan 400. |
schedule_cron | string | 0 * * * * | Ungkapan cron lima medan. Larian berturut-turut mesti berjarak sekurang-kurangnya 5 minit. |
timezone | string | UTC | Zon waktu IANA yang digunakan untuk menilai jadual, contohnya Europe/Madrid. |
notify_emails | string[] | — | Sehingga 5 alamat yang dihantar e-mel apabila pemeriksaan menemui sesuatu. Lihat Pemberitahuan. |
webhook_url | string | — | URL https awam yang menerima acara bertandatangan. Lihat Pemberitahuan. |
webhook_secret | string | dijana | 16 hingga 128 aksara yang digunakan untuk menandatangani setiap penghantaran. Dijana apabila ditinggalkan dan webhook_url ditetapkan. Dipulangkan semasa mencipta dan pada GET satu, tidak pernah pada senarai. |
retention_days | number | 30 | 1 hingga 365. Pemeriksaan yang lebih lama daripada ini dibersihkan; garis dasar disimpan sehingga monitor dipadamkan. |
status | string | active | active atau paused. Monitor yang dijeda mengekalkan garis dasarnya dan tiada next_run_at. |
Badan penuh
Dua sasaran, pagi hari bekerja di Madrid, e-mel serta webhook dengan rahsia anda sendiri, sejarah 90 hari:
{
"name": "competitor pricing",
"targets": [
{ "url": "https://competitor.com/pricing", "selector": ".pricing-table" },
{ "url": "https://competitor.com/changelog" }
],
"schedule_cron": "0 9 * * 1-5",
"timezone": "Europe/Madrid",
"notify_emails": ["alerts@example.com"],
"webhook_url": "https://example.com/hooks/crawlforge",
"webhook_secret": "3f9a1c7e5b2d48f0a6c1e9d7b5a3f2c4",
"retention_days": 90,
"status": "active"
}Objek monitor
Cipta, GET satu dan PATCH memulangkan monitor. next_run_at ialah slot seterusnya, last_check_at ialah masa pemeriksaan terbaru, dan estimated_credits_per_month diterangkan di bawah Pengebilan. Respons senarai menambah last_check, iaitu pemeriksaan terbaru tanpa halamannya.
{
"success": true,
"data": {
"id": "cmf9k2x1a0001s8h4b7d3q2wz",
"name": "competitor pricing",
"targets": [
{ "url": "https://competitor.com/pricing", "selector": ".pricing-table" }
],
"schedule_cron": "0 * * * *",
"timezone": "UTC",
"notify_emails": [],
"webhook_url": "https://example.com/hooks/crawlforge",
"webhook_secret": "3f9a1c7e5b2d48f0a6c1e9d7b5a3f2c4",
"status": "active",
"next_run_at": "2026-09-07T15:00:00.000Z",
"last_check_at": null,
"retention_days": 30,
"estimated_credits_per_month": 2160,
"created_at": "2026-09-07T14:12:08.000Z",
"updated_at": "2026-09-07T14:12:08.000Z"
}
}Senaraikan, jalankan sekarang, dan baca pemeriksaan
Senaraikan
curl "https://crawlforge.dev/api/v1/monitors?limit=20" \
-H "X-API-Key: cf_test_YOUR_KEY"
# Next page: pass the next_cursor from the previous response
curl "https://crawlforge.dev/api/v1/monitors?limit=20&cursor=NEXT_CURSOR" \
-H "X-API-Key: cf_test_YOUR_KEY"Jalankan sekarang
Menjalankan satu pemeriksaan serta-merta, di luar jadual, dan memulangkannya dengan halamannya. Peraturan pengebilan yang sama terpakai. Panggilan kedua semasa pemeriksaan itu masih berjalan menjawab 409 MONITOR_RUNNING.
# Run one check now and get it back with its pages
curl -X POST https://crawlforge.dev/api/v1/monitors/MONITOR_ID/run \
-H "X-API-Key: cf_test_YOUR_KEY"Pemeriksaan
# Check history (no pages)
curl "https://crawlforge.dev/api/v1/monitors/MONITOR_ID/checks?limit=20" \
-H "X-API-Key: cf_test_YOUR_KEY"
# One check, with its pages and webhook deliveries
curl https://crawlforge.dev/api/v1/monitors/MONITOR_ID/checks/CHECK_ID \
-H "X-API-Key: cf_test_YOUR_KEY"Objek pemeriksaan
Satu pemeriksaan dengan dua sasaran: jadual harga berubah dan dicaj, halaman dokumentasi menemui cabaran Cloudflare dan tidak dicaj. deliveries ialah log webhook bagi pemeriksaan itu.
{
"success": true,
"data": {
"id": "cmf9k5r8t0003s8h4m1n6p4vx",
"monitor_id": "cmf9k2x1a0001s8h4b7d3q2wz",
"started_at": "2026-09-07T15:00:02.000Z",
"finished_at": "2026-09-07T15:00:05.000Z",
"status": "completed",
"summary": { "total": 2, "new": 0, "changed": 1, "unchanged": 0, "blocked": 1, "errored": 0 },
"credits_reserved": 6,
"credits_charged": 3,
"error": null,
"pages": [
{
"url": "https://competitor.com/pricing",
"selector": ".pricing-table",
"status": "changed",
"change_percent": 12.5,
"structural_similarity": 0.9713,
"added_count": 4,
"removed_count": 2,
"added_samples": ["Pro $79 / month", "Includes 50,000 credits"],
"removed_samples": ["Pro $99 / month", "Includes 25,000 credits"],
"content_hash": "9f2c0b7e4d1a8c6f3e5b2a9d7c4f1e8b0a6d3c2f5e9b1a7d4c8f2e6b3a9d1c7f",
"error_code": null,
"error": null,
"blocked_vendor": null,
"duration_ms": 1180,
"charged": true
},
{
"url": "https://competitor.com/docs",
"selector": null,
"status": "blocked",
"change_percent": null,
"structural_similarity": null,
"added_count": null,
"removed_count": null,
"added_samples": [],
"removed_samples": [],
"content_hash": null,
"error_code": "BLOCKED",
"error": "Challenge page served by cloudflare",
"blocked_vendor": "cloudflare",
"duration_ms": 640,
"charged": false
}
],
"deliveries": [
{
"id": "cmf9k5rb20005s8h4q7w2e9rt",
"event": "monitor.page",
"url": "https://example.com/hooks/crawlforge",
"attempts": 1,
"status": "delivered",
"http_status": 200,
"error": null,
"delivered_at": "2026-09-07T15:00:05.100Z"
},
{
"id": "cmf9k5rb20006s8h4z3x8c5vb",
"event": "monitor.check.completed",
"url": "https://example.com/hooks/crawlforge",
"attempts": 1,
"status": "delivered",
"http_status": 200,
"error": null,
"delivered_at": "2026-09-07T15:00:05.400Z"
}
]
}
}Pengebilan dan anggaran bulanan
Tiada yuran setiap monitor, dan mencipta, menyenaraikan, mengemas kini atau memadam monitor tidak berkos. Setiap pemeriksaan menahan 3 credits bagi setiap sasaran, iaitu harga track_changes, dan mengekalkan 3 bagi setiap sasaran yang statusnya new, changed atau unchanged. Sasaran yang kembali blocked atau error tidak dicaj. Setiap pemeriksaan menulis satu baris track_changes ke log penggunaan.
Jika baki anda tidak dapat menampung tahanan itu, pemeriksaan direkodkan sebagai insufficient_credits dan tiada apa-apa diambil.
estimated_credits_per_month pada monitor ialah bilangan kali jadual tercetus dalam 30 hari akan datang × sasaran × 3. Ia ialah siling bagi bulan di mana setiap sasaran diambil dan dibandingkan setiap kali.
schedule_cron | Sasaran | Larian dalam 30 hari | estimated_credits_per_month |
|---|---|---|---|
0 * * * * | 1 | 720 | 2,160 |
*/15 * * * * | 1 | 2,880 | 8,640 |
0 9 * * * | 5 | 30 | 450 |
Status pemeriksaan dan halaman
Setiap pemeriksaan membawa satu status, satu summary dengan kiraan bagi setiap status halaman, dan credits_reserved / credits_charged.
status pemeriksaan | Maksud |
|---|---|
running | Pemeriksaan sedang berjalan. POST …/run menjawab 409 MONITOR_RUNNING sementara itu. |
completed | Setiap sasaran telah diproses. Baca summary dan pages. |
failed | Pemeriksaan tidak dapat diselesaikan; error menyatakan sebabnya. |
skipped_overlap | Pemeriksaan sebelumnya masih berjalan apabila slot ini tiba. Tiada apa-apa diambil. |
insufficient_credits | Baki tidak dapat menampung tahanan. Tiada apa-apa diambil. |
Setiap entri dalam pages membawa satu status. Halaman changed merangkumi change_percent, structural_similarity, added_count, removed_count dan sehingga 20 baris sampel sehingga 500 aksara dalam added_samples dan removed_samples; kiraan itulah jumlah sebenar.
status halaman | Maksud | Dicaj |
|---|---|---|
new | Tangkapan pertama sasaran ini. Garis dasar telah ditulis; belum ada apa-apa untuk dibandingkan. | Ya |
changed | Kandungan berbeza daripada pemeriksaan sebelumnya. Garis dasar bergulung ke hadapan kepada tangkapan ini. | Ya |
unchanged | Cincangan kandungan yang sama seperti pemeriksaan sebelumnya. | Ya |
blocked | Sasaran menjawab dengan dinding cabaran (Cloudflare, DataDome, PerimeterX, Akamai, Amazon, Vercel) atau dokumen yang tidak boleh digunakan. blocked_vendor menamakannya. Tidak pernah dilaporkan sebagai perubahan. | Tidak |
error | Pengambilan gagal, pemilih tidak sepadan dengan apa-apa, atau robots.txt melarang laluan itu. error_code membawa kodnya: FETCH_FAILED, SELECTOR_NOT_FOUND, ROBOTS_DISALLOWED, dan sebagainya. | Tidak |
respect_robots. Sasaran yang laluannya dilarang oleh robots.txt untuk CrawlForge dilaporkan sebagai error dengan ROBOTS_DISALLOWED pada setiap pemeriksaan dan tidak pernah dicaj.Peraturan jadual
schedule_cron ialah ungkapan cron lima medan piawai — minit, jam, hari dalam bulan, bulan, hari dalam minggu — yang dinilai dalam timezone. Dua larian berturut-turut mesti berjarak sekurang-kurangnya 5 minit, jadi * * * * * dan */2 * * * * ditolak dengan 400.
Penjadual CrawlForge bangun setiap 5 minit dan memulakan setiap pemeriksaan yang slotnya telah berlalu, jadi pemeriksaan bermula dalam masa 5 minit selepas slotnya. next_run_at pada monitor ialah slot seterusnya; menjeda monitor menetapkannya kepada null.
Monitor yang pemeriksaan sebelumnya masih berjalan apabila slot seterusnya tiba merekodkan pemeriksaan skipped_overlap dan bukannya memulakan pemeriksaan kedua.
| Ungkapan | Berjalan |
|---|---|
0 * * * * | Setiap jam, tepat pada jam (lalai). |
*/15 * * * * | Setiap 15 minit. |
0 9 * * 1-5 | 09:00 Isnin hingga Jumaat, dalam timezone. |
0 6,18 * * * | 06:00 dan 18:00 setiap hari. |
30 2 1 * * | 02:30 pada hari pertama setiap bulan. |
Pemberitahuan
E-mel. Apabila notify_emails ditetapkan, mesej dihantar hanya untuk pemeriksaan yang mempunyai sekurang-kurangnya satu halaman new, changed, blocked atau error. Pemeriksaan yang setiap sasarannya unchanged tidak menghantar apa-apa.
Webhook. Apabila webhook_url ditetapkan, setiap penghantaran ialah POST dengan badan JSON { event, id, timestamp, data } dan pengepala berikut:
| Pengepala | Nilai |
|---|---|
Content-Type | application/json |
X-Webhook-Event | monitor.page atau monitor.check.completed |
X-Webhook-ID | Id penghantaran, kekal merentas cubaan semula, supaya penerima boleh membuang pendua. |
X-Webhook-Timestamp | Masa Unix dalam milisaat ketika penghantaran dihantar. |
X-Webhook-Signature | sha256= diikuti oleh HMAC-SHA256 heks bagi badan mentah yang tepat, dikunci dengan webhook_secret. |
Dua acara. Acara halaman dihantar dahulu, kemudian ringkasan pemeriksaan.
event | Bila | data |
|---|---|---|
monitor.page | Satu bagi setiap halaman yang statusnya bukan unchanged. | { monitor: { id, name }, check_id, page, dashboard_url } — page ialah keputusan halaman penuh, termasuk sampel. |
monitor.check.completed | Setiap pemeriksaan yang selesai. | { monitor, check: { id, started_at, finished_at, status, summary, credits_charged }, pages, dashboard_url } — setiap entri dalam pages mempunyai url, selector, status, change_percent, added_count, removed_count, error_code, tanpa sampel. |
{
"event": "monitor.check.completed",
"id": "cmf9k5rb20006s8h4z3x8c5vb",
"timestamp": 1788793205400,
"data": {
"monitor": { "id": "cmf9k2x1a0001s8h4b7d3q2wz", "name": "competitor pricing" },
"check": {
"id": "cmf9k5r8t0003s8h4m1n6p4vx",
"started_at": "2026-09-07T15:00:02.000Z",
"finished_at": "2026-09-07T15:00:05.000Z",
"status": "completed",
"summary": { "total": 2, "new": 0, "changed": 1, "unchanged": 0, "blocked": 1, "errored": 0 },
"credits_charged": 3
},
"pages": [
{
"url": "https://competitor.com/pricing",
"selector": ".pricing-table",
"status": "changed",
"change_percent": 12.5,
"added_count": 4,
"removed_count": 2,
"error_code": null
},
{
"url": "https://competitor.com/docs",
"selector": null,
"status": "blocked",
"change_percent": null,
"added_count": null,
"removed_count": null,
"error_code": "BLOCKED"
}
],
"dashboard_url": "https://www.crawlforge.dev/dashboard/monitors/cmf9k2x1a0001s8h4b7d3q2wz"
}
}Penghantaran. Sehingga 4 cubaan — yang pertama serta tiga cubaan semula — dengan 1 s, 2 s dan 4 s di antaranya dan tamat masa 10 s setiap satu. Respons 2xx dikira sebagai dihantar. 4xx selain 408 atau 429 tidak dicuba semula. Log penghantaran disertakan bersama pemeriksaan: GET …/checks/{check_id} memulangkan deliveries dengan kiraan cubaan, status HTTP dan ralat bagi setiap acara.
Mengesahkan tandatangan
Kira HMAC ke atas badan permintaan mentah, sebelum sebarang penghuraian JSON, dan bandingkan dalam masa malar. Set pengepala dan skema tandatangan adalah sama seperti yang digunakan oleh webhook pelayan MCP CrawlForge, jadi satu penerima mengesahkan kedua-duanya.
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
// Sign the RAW body exactly as received. Parsing and re-serialising the JSON
// first changes the bytes and the signature no longer matches.
function verifyCrawlForgeWebhook(rawBody: Buffer, signatureHeader: string, secret: string): boolean {
const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex'));
const received = Buffer.from(signatureHeader);
return expected.length === received.length && timingSafeEqual(expected, received);
}
const app = express();
// express.raw keeps the body as a Buffer; express.json would have parsed it.
app.post('/hooks/crawlforge', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.CRAWLFORGE_WEBHOOK_SECRET!;
if (!verifyCrawlForgeWebhook(req.body, req.header('X-Webhook-Signature') ?? '', secret)) {
return res.status(401).end();
}
const { event, data } = JSON.parse(req.body.toString('utf8'));
if (event === 'monitor.page') {
console.log(data.page.status, data.page.url, data.page.change_percent);
} else if (event === 'monitor.check.completed') {
console.log('check', data.check.id, data.check.summary);
}
res.status(204).end();
});Had
- 50 monitor bagi setiap akaun. Penciptaan ke-51 menjawab 409
MONITOR_LIMIT_REACHED. - 20 sasaran bagi setiap monitor dan 5 alamat dalam
notify_emails. - Sekurang-kurangnya 5 minit antara larian berturut-turut.
retention_daysdari 1 hingga 365, lalai 30. Garis dasar disimpan sehingga monitor dipadamkan.- Sampel perbezaan: sehingga 20 baris sehingga 500 aksara dalam setiap
added_samplesdanremoved_samples.
Ralat
| Status | Maksud |
|---|---|
400 VALIDATION_ERROR | Sesuatu medan di luar julat, ungkapan cron tidak sah atau tercetus lebih kerap daripada setiap 5 minit, atau URL sasaran bukan alamat http(s) awam. |
| 401 | API key tiada atau tidak sah. |
| 404 | Tiada monitor atau pemeriksaan dengan id itu pada akaun anda. |
409 MONITOR_LIMIT_REACHED | Anda sudah mempunyai 50 monitor. |
409 MONITOR_RUNNING | POST …/run semasa pemeriksaan sedang berjalan. |
operation: "monitor" untuk mencipta monitor daripada satu URL; create_scheduled_monitor pelayan MCP dengan hosted: true mencipta monitor jenis yang sama.