Create and manage everything you monitor — sites, HTML/SEO changes, stuck-content detection and HTTP status codes — from your own code or an AI agent. More monitor types land on this same surface as they ship.
https://pageradar.io/api/v1
Create a token →
Pick a preset or individual scopes
OpenAPI 3.1 spec →
Machine-readable contract
Using an AI agent?
Point it at this page — it is written to be read by both
Ownership. Every id you pass is resolved against your own account. Anything belonging to
someone else answers 404, exactly like an id that does not exist — so there is
nothing to learn from probing.
Send your token as a Bearer credential on every request:
curl https://pageradar.io/api/v1/sites \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"
A token carries scopes, chosen when you create it at API access. Each endpoint below states the scope it needs.
| Scope | Grants |
|---|---|
account:read | Read your account identity |
sites:read | List and read sites |
sites:write | Create sites |
sites:delete | Delete a site and every monitor under it. Not in the Manager preset — pick it explicitly |
monitors:read | List and read monitors |
monitors:write | Create and update monitors |
monitors:delete | Delete a monitor and its history |
alerts:read | Read alert lists |
checks:run | Trigger on-demand checks |
Scopes are frozen when a token is created. A 403 insufficient_scope
names the scope you are missing; an existing token cannot gain it, so create a new one. Tokens issued before
scopes existed are read-only on v1.
Limits apply per account, per class of operation, on top of an overall 100 req/min ceiling.
| Class | Limit | Applies to |
|---|---|---|
| Reads | 100 / min | Every GET |
| Writes | 30 / min | POST, PATCH, DELETE |
| Check runs | 6 / min | On-demand checks, plus a daily budget per plan |
A 429 carries Retry-After. Honor it —
rate_limited is the per-minute wall, run_budget_exhausted
the daily check budget. Scheduled checks keep running either way; the budget only limits manual triggers.
Errors are RFC 9457
problem documents. Branch on code, show detail to a
human, and quote request_id when reporting a problem.
{
"type": "https://pageradar.io/problems/quota_exceeded",
"title": "Quota exceeded",
"status": 422,
"detail": "Quota exceeded",
"code": "quota_exceeded",
"request_id": "bff36e1c-e584-4bf8-a86a-f2bb1b69613a",
"errors": {
"quota": { "monitor_type": "html_change", "limit": 10, "used": 10, "remaining": 0, "requested": 1 }
}
}
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated | Missing or invalid token |
| 403 | insufficient_scope | The token lacks the scope this endpoint needs |
| 404 | not_found | Unknown id — or one that is not yours |
| 409 | idempotency_in_flight | The first request with this key is still running; retry shortly |
| 422 | validation_error | Field errors in errors |
| 422 | quota_exceeded | Your plan has no room; errors.quota gives the numbers |
| 422 | idempotency_key_conflict | Key reused with a different payload — use a new key |
| 429 | rate_limited | Per-minute limit; honor Retry-After |
| 429 | run_budget_exhausted | Daily on-demand check budget spent |
| 503 | quota_locked | Transient contention on a concurrent write; retry |
Lists are cursor-paginated: follow links.next until it is
null. Cursors stay correct while rows are being inserted, which page numbers do not.
per_page defaults to 25, maximum 100.
{
"data": [ /* … */ ],
"links": { "first": null, "last": null, "prev": null, "next": "https://…?cursor=eyJpZCI6NDJ9" },
"meta": { "path": "…", "per_page": 25 }
}
Creation and check-run endpoints accept an optional Idempotency-Key header. Send one
whenever a retry is possible: the original response is replayed for 24 hours instead of the work happening twice.
Idempotency-Replayed: true.422 idempotency_key_conflict. Mint one key per distinct request.-H "Idempotency-Key: $(uuidgen)"
Endpoints · Sites
A site groups everything you monitor for one domain. Create one first — every monitor hangs off it.
/v1/sites
scope
sites:read
Your sites, newest first. Cursor-paginated.
{
"data": [
{ "id": 12, "name": "Example", "domain": "example.com",
"created_at": "2026-08-01T10:00:00+00:00", "updated_at": "2026-08-01T10:00:00+00:00" }
],
"links": { "next": null }
}
/v1/sites
scope
sites:write
Domains are normalized before storage — scheme and www are stripped, so
https://www.example.com/ and example.com are the
same site. A domain you already have is refused with
422 validation_error rather than silently creating a second one; list your
sites first, or treat that 422 as "already there".
Adding a page that is already monitored behaves differently, and more forgivingly: it is
reported in skipped_duplicates and costs nothing. You can re-send the same
list of URLs safely — see Create monitors.
| Field | Type | Description | |
|---|---|---|---|
domain |
string | required | The domain to monitor, e.g. example.com. Max 255 characters. |
name |
string | optional | A label for your dashboard. Defaults to the domain. |
curl -X POST https://pageradar.io/api/v1/sites \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"domain": "example.com", "name": "Example"}'
{
"data": { "id": 12, "name": "Example", "domain": "example.com",
"created_at": "2026-09-01T09:12:00+00:00", "updated_at": "2026-09-01T09:12:00+00:00" }
}
/v1/sites/{site}
scope
sites:read
One of your sites. 404 if the id is not yours.
/v1/sites/{site}
scope
sites:delete
The widest-reaching call in this API. It removes the site and every monitor under it — HTML, status code, Core Web Vitals urls, sitemap, robots.txt, keywords, screenshots, affiliate trackers — with all of their history. There is no undo and no trash. Confirm with a human first, and prefer deleting individual monitors.
The response states exactly what went with it, and the capacity freed per monitor type. A second call on the
same id is a 404, so retries are safe.
{
"data": {
"id": 12, "domain": "example.com",
"deleted": { "html_monitors": 3, "status_code_monitors": 5, "urls": 2,
"sitemap_monitors": 0, "robots_txt_monitors": 1, "keyword_trackers": 0,
"screenshot_monitors": 0, "affiliate_redirect_trackers": 0 }
},
"meta": {
"quota": { "html_change": { "limit": 10, "used": 4, "remaining": 6 },
"status_code": { "limit": 20, "used": 9, "remaining": 11 } }
}
}
Endpoints · HTML & SEO monitors
Watch a page for changes to its SEO elements — title, meta description, canonical, headings, Open Graph, schema — and optionally detect a page whose content has stopped moving (see freshness).
/v1/sites/{site}/html-monitors
scope
monitors:read
/v1/sites/{site}/html-monitors
scope
monitors:write
Creates one monitor per URL. The response is multi-status: each URL comes back as created,
skipped (already monitored) or refused for lack of quota — a partial success is a normal outcome, not an error.
201 when anything was created, 200 when everything was
a duplicate, 422 quota_exceeded when nothing fits. Monitors are created active and
their first check is queued immediately.
| Field | Type | Description | |
|---|---|---|---|
urls |
string[] | required | 1 to 100 URLs to monitor. |
check_frequency |
enum | optional | hourly or daily. Default daily. |
elements_to_monitor |
string[] | optional | e.g. title, meta_description, canonical, h1, open_graph. |
monitor_full_html |
boolean | optional | Hash the whole page instead of the parsed elements. |
focus_keywords |
string[] | optional | Flag changes that affect these keywords. |
excluded_properties |
string[] | optional | Elements to ignore when diffing. |
tags |
string[] | optional | Your own labels. |
email_notifications_enabled |
boolean | optional | Default true. |
slack_notifications_enabled |
boolean | optional | Default false. |
slack_webhook_url |
string | optional | Required if Slack notifications are on. |
freshness_check_enabled |
boolean | optional | Turn on stuck-content detection. |
freshness_threshold_hours |
integer | conditional | Required with freshness. 1–168; at least 24 unless check_frequency is hourly. |
freshness_url_pattern |
string | optional | Regex selecting which content links count, e.g. #^/\d{4}/# |
curl -X POST https://pageradar.io/api/v1/sites/12/html-monitors \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"urls": ["https://example.com/news"], "check_frequency": "hourly",
"freshness_check_enabled": true, "freshness_threshold_hours": 12}'
{
"data": {
"created": [ { "id": 88, "type": "html_change", "url": "https://example.com/news",
"is_active": true, "check_frequency": "hourly",
"freshness": { "enabled": true, "threshold_hours": 12, "url_pattern": null,
"frozen_since": null, "unchanged_checks": 0, "last_new_content_at": null },
"last_checked_at": null } ],
"skipped_duplicates": [],
"quota_exceeded": []
},
"meta": { "quota": { "limit": 10, "used": 4, "remaining": 6 } }
}
/v1/html-monitors/{monitor}
scope
monitors:read
Includes the live freshness state. This is also the endpoint you poll after triggering a check:
last_checked_at advances when it completes.
/v1/html-monitors/{monitor}
scope
monitors:write
Send only the keys you want to change.
Editable:
is_active, check_frequency,
monitor_full_html, elements_to_monitor,
excluded_properties, focus_keywords,
custom_settings, tags,
email_notifications_enabled, slack_notifications_enabled,
slack_webhook_url, freshness_check_enabled,
freshness_threshold_hours, freshness_url_pattern.
Not editable: url — it identifies the monitor; delete and
recreate to watch another page. Unknown keys are ignored rather than rejected.
Quota counts active monitors. Switching is_active from false to true
consumes a slot and can return 422 quota_exceeded; switching it off always
works and frees the slot while keeping the history.
curl -X PATCH https://pageradar.io/api/v1/html-monitors/88 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"check_frequency": "daily", "freshness_threshold_hours": 48}'
/v1/html-monitors/{monitor}
scope
monitors:delete
Permanent: the monitor and all of its snapshots and alerts are gone. Prefer
PATCH {"is_active": false} when the intent is only to pause monitoring — it frees
the quota slot too, and the history survives. Returns the freed quota; a second call is a
404.
/v1/html-monitors/{monitor}/alerts
scope
alerts:read
Cursor-paginated, newest first. severity is one of
critical, high, medium,
low. Before/after values are clipped at 2000 characters, with
values_truncated telling you when that happened.
{
"data": [
{ "id": 501, "html_monitor_id": 88, "change_type": "modified", "element_type": "title",
"old_value": "Old title", "new_value": "New title", "values_truncated": false,
"severity": "high", "focus_keyword_affected": null, "is_read": false,
"detected_at": "2026-09-01T07:15:00+00:00" }
],
"links": { "next": null }
}
/v1/html-monitors/{monitor}/freshness-alerts
scope
alerts:read
/v1/html-monitors/{monitor}/check
scope
checks:run
Queues one check. There is no run id: on 202, poll
GET /v1/html-monitors/{'{'}monitor{'}'} until
last_checked_at advances, then read the alert lists. Polling every 15–30 seconds
is plenty. Do not poll by calling this endpoint again.
| Field | Type | Description | |
|---|---|---|---|
202 queued |
The check is on its way. | ||
200 not_run · inactive |
Activate the monitor first (PATCH is_active=true). | ||
200 not_run · already_queued |
A check is already in flight; meta.retry_after_seconds is the worst-case wait. | ||
422 quota_exceeded |
More active monitors than the plan allows. | ||
429 run_budget_exhausted |
Daily on-demand budget spent; honor Retry-After. |
A news page, a blog index or a category listing is supposed to keep producing new links. When it stops — a broken feed, a stalled publishing pipeline — nothing errors: the page still returns 200 with valid HTML. Freshness mode watches the content links of a page and tells you when they stop changing.
Turn it on at creation or with a PATCH:
| Field | Type | Description | |
|---|---|---|---|
freshness_check_enabled |
boolean | required | Turns the mode on. |
freshness_threshold_hours |
integer | required | How long without new content is too long. 1–168 hours; at least 24 unless check_frequency is hourly. |
freshness_url_pattern |
string | optional | Regex restricting which links count as content, e.g. #^/\d{4}/# for dated article URLs. |
Then read the state on the monitor:
"freshness": {
"enabled": true,
"threshold_hours": 12,
"url_pattern": "#^/\\d{4}/#",
"unchanged_checks": 4, // consecutive checks with no new content
"frozen_since": "2026-08-31T18:00:00+00:00", // non-null once the page is considered stuck
"last_new_content_at": "2026-08-31T06:00:00+00:00"
}
Do not expect an alert right after enabling it. Freezing needs at least three consecutive
unchanged checks plus the threshold, so the signal comes from state moving over time — watch
unchanged_checks and frozen_since, not an immediate
notification.
Endpoints · HTTP status monitors
Uptime and status-code monitoring: know when a URL starts answering 404, 500 or a redirect it should not.
/v1/sites/{site}/status-monitors
scope
monitors:read
/v1/sites/{site}/status-monitors
scope
monitors:write
Multi-status, exactly like HTML monitors: created / skipped / quota-refused per URL.
| Field | Type | Description | |
|---|---|---|---|
urls |
string[] | required | 1 to 100 URLs to monitor. |
check_frequency |
enum | optional | hourly, daily or weekly. Default hourly. |
tags |
string[] | optional | Your own labels. |
slack_notifications_enabled |
boolean | optional | Default false. |
slack_webhook_url |
string | optional | Required if Slack notifications are on. |
additional_notification_emails |
string[] | optional | Up to 2 extra recipients, on top of your account email. |
send_confirmation_email |
boolean | optional | Email you a creation summary. Off by default for API calls. |
curl -X POST https://pageradar.io/api/v1/sites/12/status-monitors \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"urls": ["https://example.com/checkout"], "check_frequency": "hourly"}'
/v1/status-monitors/{monitor}
scope
monitors:read
current_status_code is null until the first check
completes. 0 is the reserved value for a connection error — a site that could not
be reached at all.
{
"data": { "id": 140, "type": "status_code", "site_id": 12,
"url": "https://example.com/checkout", "is_active": true,
"check_frequency": "hourly",
"current_status_code": 200, "previous_status_code": 503,
"notifications": { "slack_enabled": false, "slack_webhook_url": null, "additional_emails": null },
"tags": null, "last_checked_at": "2026-09-01T08:00:00+00:00" }
}
/v1/status-monitors/{monitor}
scope
monitors:write
Editable:
is_active, check_frequency,
tags, slack_notifications_enabled,
slack_webhook_url, additional_notification_emails.
Not editable: url. Same quota rules on activation as HTML monitors.
curl -X PATCH https://pageradar.io/api/v1/status-monitors/140 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"check_frequency": "weekly", "slack_notifications_enabled": true,
"slack_webhook_url": "https://hooks.slack.com/services/XXX"}'
/v1/status-monitors/{monitor}
scope
monitors:delete
Permanent, with its alert history. Prefer deactivating to pause.
/v1/status-monitors/{monitor}/alerts
scope
alerts:read
change_type classifies the transition:
| Field | Type | Description | |
|---|---|---|---|
critical |
2xx → 4xx/5xx | ||
recovery |
4xx/5xx → 2xx | ||
redirect |
2xx → 3xx | ||
change |
Everything else — including transitions to and from 0 |
Detecting real downtime: a site that becomes completely unreachable records status
0, and 200 → 0 is classified
change, not critical. Test the codes themselves:
new_status_code === 0 || new_status_code >= 400.
/v1/status-monitors/{monitor}/check
scope
checks:run
Same semantics as the HTML check: 202 then poll the monitor. Transient errors
(timeouts, 5xx) are re-confirmed about two minutes later before being recorded, so allow roughly three minutes
before concluding anything.
Endpoints · Affiliate link monitoring
Trace an affiliate link's full redirect chain from a specific country, and get told when the chain changes, when the final destination moves, or when a network starts blocking you.
Read this before automating at scale. Checks here cost credits, not just quota:
next_check_at is pushed there).
This is a proxy-network capability, not the ISO list: a valid ISO code that is not here is a
422. The machine-readable version is the
country_code enum in
openapi.json.
/v1/sites/{site}/affiliate-trackers
scope
monitors:read
/v1/sites/{site}/affiliate-trackers
scope
monitors:write
One tracker per URL, for one country. The dedup identity is the triple
(site, url, country): the same link in another country is a new tracker, the same triple is skipped.
Multi-status — 201 when anything was created,
200 when everything was already tracked.
| Field | Type | Description | |
|---|---|---|---|
urls |
string[] | required | Up to 100 — lower than the dashboard on purpose, since each one queues a check that costs a credit. |
country_code |
string | required | ISO 3166-1 alpha-2 from the supported list. An unsupported code is a 422. |
name |
string | optional | Base name, suffixed #1, #2… with several URLs. Defaults to the link host. |
check_frequency_hours |
integer | optional | 24 (daily), 48 or 168 (weekly). Default 24. |
tags |
string[] | optional | Your own labels. |
alert_settings |
object | optional | redirect_change, final_domain_change, status_error, blocked_detected — all default true. timeout is always on and cannot be disabled. |
curl -X POST https://pageradar.io/api/v1/sites/12/affiliate-trackers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"urls": ["https://partner.example.com/go/abc"],
"country_code": "ES", "name": "Bookmaker", "check_frequency_hours": 24}'
{
"data": {
"created": [ { "id": 501, "type": "affiliate_redirect", "url": "https://partner.example.com/go/abc",
"country_code": "ES", "is_active": true, "check_frequency_hours": 24 } ],
"skipped_duplicates": []
},
"meta": { "credits": { "limit": 5000, "used": 1204, "remaining": 3796,
"period_end": "2026-09-30T23:59:59+00:00",
"checks_queued": 1 } }
}
Reading meta.credits correctly: credits are spent when a check
runs, not when a tracker is created — so used does not yet include the
checks this call just queued. checks_queued tells you how many are about to be
debited.
/v1/affiliate-trackers/{tracker}
scope
monitors:read
country_code is ISO 3166-1 alpha-2 and immutable;
next_check_at tells you when the scheduler will look again.
{
"data": { "id": 501, "type": "affiliate_redirect", "site_id": 12,
"name": "Bookmaker ES", "url": "https://partner.example.com/go/abc",
"country_code": "ES", "is_active": true, "check_frequency_hours": 24,
"alert_settings": { "redirect_change": true, "final_domain_change": true,
"status_error": true, "timeout": true, "blocked_detected": true },
"tags": ["sports"], "last_checked_at": "2026-09-08T06:30:00+00:00",
"next_check_at": "2026-09-09T06:30:00+00:00" }
}
/v1/affiliate-trackers/{tracker}
scope
monitors:write
Editable:
is_active, name,
check_frequency_hours (24 / 48 / 168),
tags, alert_settings.
Not editable: initial_url and
country_code. With the site they are the tracker's identity, and the stored
checks were run against them — changing either would duplicate an existing tracker and orphan its history.
Delete and recreate instead. alert_settings is merged, so a partial
PATCH keeps the toggles it does not mention.
To stop spending credits on a link, deactivate it
({"is_active": false}) rather than deleting it: the checks stop and the
history stays.
curl -X PATCH https://pageradar.io/api/v1/affiliate-trackers/501 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"check_frequency_hours": 168, "alert_settings": {"status_error": false}}'
/v1/affiliate-trackers/{tracker}
scope
monitors:delete
Permanent: every executed check and every alert go with it. A second call is a
404, so retries are safe. Prefer deactivating to pause.
/v1/affiliate-trackers/{tracker}/checks
scope
results:read
One entry per executed check, newest first, with the hop-by-hop chain as observed from the tracker's
country. final_domain is the field to watch: a change
there is how affiliate link hijacking shows up. final_status_code is
0 when the chain could not be completed at all.
The chain is third-party content, so it is bounded: withheld entirely when the stored chain exceeds ~64 KB,
and otherwise capped at 25 hops. redirect_chain_truncated tells you when what you
see is not the whole story.
{
"data": [
{ "id": 9001, "tracker_id": 501, "final_status_code": 200,
"final_domain": "bookmaker.example", "total_redirects": 3,
"total_response_time_ms": 812, "is_successful": true, "error_message": null,
"redirect_chain": [
{ "url": "https://partner.example.com/go/abc", "status_code": 302, "host": "partner.example.com" },
{ "url": "https://track.example.net/c/1", "status_code": 302, "host": "track.example.net" },
{ "url": "https://bookmaker.example/es/", "status_code": 200, "host": "bookmaker.example" }
],
"redirect_chain_truncated": false,
"checked_at": "2026-09-08T06:30:12+00:00" }
],
"links": { "next": null }
}
/v1/affiliate-trackers/{tracker}/check
scope
checks:run
Queues one check of the redirect chain from the tracker's country. It spends one credit.
There is no run id: on 202, poll
GET /v1/affiliate-trackers/{'{'}tracker{'}'} until
last_checked_at advances, then read /checks.
| Field | Type | Description | |
|---|---|---|---|
202 queued |
On its way. One credit will be spent when it runs. | ||
200 not_run · inactive |
Activate the tracker first (PATCH is_active=true). | ||
200 not_run · already_queued |
A check is already in flight; poll the tracker instead. | ||
429 rate_limited |
Per-minute burst limit (6/min). Retry in seconds. | ||
429 run_budget_exhausted |
Shared DAILY on-demand budget across all monitor types. Retry tomorrow. | ||
429 affiliate_credits_exhausted |
MONTHLY credit allowance spent. errors.credits.period_end says when it resets — and your SCHEDULED checks are paused until then too. |
Three different 429s, three different waits: seconds for the burst limit,
a day for the run budget, until the 1st of next month for credits.
Branch on code, never on the status alone.
/v1/affiliate-trackers/{tracker}/alerts
scope
alerts:read
| Field | Type | Description | |
|---|---|---|---|
status_change |
The final destination now answers a different status code. | ||
domain_change |
The link now lands on a different domain — check for hijacking. | ||
redirect_count_change |
The chain gained or lost at least 2 hops. | ||
performance_degradation |
The chain became more than twice as slow, and over 10 seconds. | ||
potential_block |
The chain used to complete and no longer does — the network may be blocking that country. | ||
redirect_path_change |
The set of intermediate domains changed. |
Severity is one of critical, high, medium, low. Email is sent for critical and high only.
/v1/user
scope
account:read
The account behind the token. Useful as a connectivity and scope check.
{ "data": { "id": 1, "name": "Sam", "email": "[email protected]" } }
Watch a news page and get told when it stops publishing:
# 1. the site
curl -sX POST https://pageradar.io/api/v1/sites \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"domain": "example.com"}' # -> data.id = SITE_ID
# 2. an hourly monitor with stuck-content detection
curl -sX POST https://pageradar.io/api/v1/sites/$SITE_ID/html-monitors \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"urls": ["https://example.com/news"], "check_frequency": "hourly",
"freshness_check_enabled": true, "freshness_threshold_hours": 12}'
# 3. later: is the page stuck?
curl -s https://pageradar.io/api/v1/html-monitors/$MONITOR_ID \
-H "Authorization: Bearer $TOKEN" # -> data.freshness.frozen_since
La première API de PageRadar, antérieure à la v1. Toujours supportée et inchangée ; les nouvelles intégrations doivent utiliser la v1 ci-dessus.
Accédez à vos données de monitoring PageRadar par programmation. Créez des tableaux de bord personnalisés, intégrez vos outils et automatisez vos workflows de veille de marque.
https://pageradar.io/api
6
Points d'accès
100/min
Limite
REST
JSON API
Toutes les requêtes API nécessitent un token Bearer dans l'en-tête Authorization.
Abonnement requis. Un abonnement PageRadar actif est nécessaire pour utiliser l'API. Voir les tarifs
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json" \
https://pageradar.io/api/reddit/monitors
L'API autorise 100 requêtes par minute par utilisateur authentifié. Les informations de limite sont incluses dans les en-têtes de réponse :
| En-tête | Description |
|---|---|
X-RateLimit-Limit |
Nombre maximum de requêtes par minute (100) |
X-RateLimit-Remaining |
Requêtes restantes dans la fenêtre actuelle |
Retry-After |
Secondes à attendre (uniquement sur les réponses 429) |
HTTP/1.1 429 Too Many Requests
Retry-After: 42
{
"message": "Too Many Attempts."
}
Les endpoints de liste renvoient des résultats paginés avec des objets meta et links pour la navigation.
| Paramètre | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Numéro de page |
per_page |
integer | 50 | Éléments par page (max 100) |
{
"data": [ ... ],
"links": {
"first": "https://pageradar.io/api/reddit/mentions?page=1",
"last": "https://pageradar.io/api/reddit/mentions?page=5",
"prev": null,
"next": "https://pageradar.io/api/reddit/mentions?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"per_page": 50,
"to": 50,
"total": 237
}
}
| Code | Signification | Description |
|---|---|---|
| 200 | OK | Requête réussie |
| 401 | Non autorisé | Token API manquant ou invalide |
| 403 | Interdit | Pas d'abonnement actif — mettre à niveau |
| 404 | Non trouvé | La ressource n'existe pas ou ne vous appartient pas |
| 422 | Erreur de validation | Paramètres invalides — vérifiez l'objet errors dans la réponse |
| 429 | Trop de requêtes | Limite dépassée — attendez puis réessayez |
| 500 | Erreur serveur | Un problème est survenu de notre côté |
401 Unauthorized
{
"message": "Unauthenticated."
}
403 Forbidden
{
"error": "subscription_required",
"message": "An active subscription is required.",
"upgrade_url": "https://pageradar.io/pricing"
}
Points d'accès
/api/reddit/monitors
Renvoie tous les monitors de mots-clés Reddit de l'utilisateur authentifié, avec le nombre de mentions et d'alertes non lues.
{
"data": [
{
"id": 1,
"keyword": "pageradar",
"subreddits": ["seo", "webdev"],
"check_frequency": "hourly",
"is_active": true,
"last_checked_at": "2026-03-19T08:00:00+00:00",
"mentions_count": 42,
"unread_alerts_count": 3,
"created_at": "2026-01-15T10:30:00+00:00",
"updated_at": "2026-03-19T08:00:00+00:00"
}
]
}
/api/reddit/monitors/{id}
Renvoie les détails d'un monitor. Renvoie 404 si non trouvé ou s'il ne vous appartient pas.
| Paramètre | Type | Description |
|---|---|---|
id | integer | ID du monitor requis |
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://pageradar.io/api/reddit/monitors/1
/api/reddit/monitors/{id}/mentions
Renvoie les mentions paginées d'un monitor spécifique. Supporte les mêmes filtres que Toutes les mentions.
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://pageradar.io/api/reddit/monitors/1/mentions?subreddit=seo&per_page=20"
/api/reddit/mentions
Renvoie toutes les mentions Reddit de tous vos monitors, avec filtrage, tri et pagination.
| Paramètre | Type | Default | Description |
|---|---|---|---|
monitor_id |
integer | - | Filtrer par ID de monitor |
keyword |
string | - | Recherche dans keyword_found (correspondance partielle) |
subreddit |
string | - | Filtrer par nom de subreddit (ex. seo) |
found_in |
string | - | Où le mot-clé a été trouvé : title, body ou comment |
date_from |
date | - | Date de début (YYYY-MM-DD) |
date_to |
date | - | Date de fin (YYYY-MM-DD) |
sort |
string | date | date, score, or comments |
sort_dir |
string | desc | asc or desc |
per_page |
integer | 50 | Résultats par page (max 100) |
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://pageradar.io/api/reddit/mentions?subreddit=seo&date_from=2026-01-01&sort=score&per_page=20"
{
"data": [
{
"id": 123,
"monitor_id": 1,
"keyword_found": "pageradar",
"reddit_id": "abc123",
"post_title": "Best SEO monitoring tools in 2026?",
"post_url": "https://reddit.com/r/seo/comments/abc123",
"subreddit": "seo",
"author": "seo_expert",
"found_in": "body",
"context_text": "I've been using pageradar for my clients...",
"score": 42,
"num_comments": 15,
"comment_id": null,
"comment_author": null,
"created_at_reddit": "2026-03-18T14:30:00+00:00",
"created_at": "2026-03-18T15:00:00+00:00"
}
],
"links": { "first": "...?page=1", "last": "...?page=3", "prev": null, "next": "...?page=2" },
"meta": { "current_page": 1, "last_page": 3, "per_page": 50, "total": 142 }
}
/api/reddit/mentions/{id}
Renvoie une mention avec tous ses détails. Renvoie 404 si non trouvée ou si elle ne vous appartient pas.
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://pageradar.io/api/reddit/mentions/123
/api/reddit/search
Live
Recherchez sur Reddit en temps réel n'importe quel mot-clé. Cet endpoint interroge directement l'API Reddit et renvoie les posts correspondants — aucun monitor nécessaire.
Idéal pour les vérifications ponctuelles de marque, la prospection ou la création de tableaux de bord personnalisés.
| Paramètre | Type | Default | Description |
|---|---|---|---|
keyword |
string | - | Mot-clé à rechercher requis |
subreddits[] |
array | all | Limiter à des subreddits spécifiques (e.g. subreddits[]=seo&subreddits[]=webdev) |
sort |
string | new | new, relevance, hot, top, or comments |
time |
string | week | hour, day, week, month, year, or all |
limit |
integer | 25 | Nombre de posts Reddit à scanner (max 100) |
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://pageradar.io/api/reddit/search?keyword=elon+musk&time=day&limit=25"
{
"data": [
{
"reddit_id": "1ry61df",
"post_title": "what's the cringiest fact about elon musk?",
"post_url": "https://reddit.com/r/AskReddit/comments/1ry61df",
"subreddit": "AskReddit",
"author": "Thick-Topic-521",
"found_in": "title",
"context_text": "what's the cringiest fact about elon musk?",
"keyword_found": "elon musk",
"score": 2,
"num_comments": 4,
"created_at_reddit": "2026-03-19T17:01:26+00:00"
}
],
"keyword": "elon musk",
"subreddits": "all",
"sort": "new",
"time": "day",
"total": 8
}
/api/user
Renvoie les informations de base de l'utilisateur authentifié. Utile pour vérifier que votre token fonctionne.
{
"id": 1,
"name": "John Doe",
"email": "[email protected]"
}
| Champ | Type | Description |
|---|---|---|
id | integer | Identifiant unique du monitor |
keyword | string | Mot-clé surveillé |
subreddits | array|null | Liste de subreddits spécifiques, ou null pour tous |
check_frequency | string | hourly, daily, or weekly |
is_active | boolean | Si le monitor est actif |
last_checked_at | string|null | Date/heure ISO 8601 du dernier check |
mentions_count | integer | Nombre total de mentions trouvées |
unread_alerts_count | integer | Nombre d'alertes non lues |
created_at | string | Date de création ISO 8601 |
updated_at | string | Date de dernière mise à jour ISO 8601 |
| Champ | Type | Description |
|---|---|---|
id | integer | Identifiant unique de la mention |
monitor_id | integer | ID du monitor parent |
keyword_found | string | Le mot-clé exact qui a matché |
reddit_id | string | ID du post Reddit |
post_title | string | Titre du post Reddit |
post_url | string | URL directe vers le post Reddit |
subreddit | string | Nom du subreddit (sans le préfixe r/) |
author | string|null | Nom d'utilisateur Reddit de l'auteur |
found_in | string | title, body, or comment |
context_text | string|null | Extrait de texte autour du mot-clé trouvé |
score | integer | Score de votes Reddit |
num_comments | integer | Nombre de commentaires sur le post |
comment_id | string|null | ID du commentaire (si trouvé dans un commentaire) |
comment_author | string|null | Auteur du commentaire (si trouvé dans un commentaire) |
created_at_reddit | string|null | Date de création du post sur Reddit (ISO 8601) |
created_at | string | Date de détection de la mention (ISO 8601) |
Lister tous les monitors
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
https://pageradar.io/api/reddit/monitors
Rechercher des mentions avec filtres
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
"https://pageradar.io/api/reddit/mentions?subreddit=seo&sort=score&date_from=2026-01-01&per_page=20"
Recherche en direct d'un mot-clé
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
"https://pageradar.io/api/reddit/search?keyword=your+brand&time=day&limit=50"
Lister tous les monitors
const API_TOKEN = 'YOUR_TOKEN';
const BASE_URL = 'https://pageradar.io/api';
const response = await fetch(`${BASE_URL}/reddit/monitors`, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Accept': 'application/json',
},
});
const { data } = await response.json();
data.forEach(monitor => {
console.log(`${monitor.keyword}: ${monitor.mentions_count} mentions`);
});
Rechercher des mentions avec filtres
const params = new URLSearchParams({
subreddit: 'seo',
sort: 'score',
date_from: '2026-01-01',
per_page: '20',
});
const response = await fetch(`${BASE_URL}/reddit/mentions?${params}`, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Accept': 'application/json',
},
});
const { data, meta } = await response.json();
console.log(`Found ${meta.total} mentions (page ${meta.current_page}/${meta.last_page})`);
Recherche en direct d'un mot-clé
const params = new URLSearchParams({
keyword: 'your brand',
time: 'day',
limit: '50',
});
const response = await fetch(`${BASE_URL}/reddit/search?${params}`, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Accept': 'application/json',
},
});
const { data, total } = await response.json();
console.log(`Found ${total} live mentions`);
data.forEach(mention => {
console.log(`[r/${mention.subreddit}] ${mention.post_title} (score: ${mention.score})`);
});
Lister tous les monitors
import requests
API_TOKEN = "YOUR_TOKEN"
BASE_URL = "https://pageradar.io/api"
headers = {"Authorization": f"Bearer {API_TOKEN}", "Accept": "application/json"}
response = requests.get(f"{BASE_URL}/reddit/monitors", headers=headers)
monitors = response.json()["data"]
for m in monitors:
print(f"{m['keyword']}: {m['mentions_count']} mentions, {m['unread_alerts_count']} unread")
Rechercher des mentions avec filtres
params = {
"subreddit": "seo",
"sort": "score",
"date_from": "2026-01-01",
"per_page": 100,
}
response = requests.get(f"{BASE_URL}/reddit/mentions", headers=headers, params=params)
data = response.json()
for mention in data["data"]:
print(f"r/{mention['subreddit']}: {mention['post_title']} (score: {mention['score']})")
print(f"\nTotal: {data['meta']['total']} mentions across {data['meta']['last_page']} pages")
Recherche en direct d'un mot-clé
params = {
"keyword": "your brand",
"time": "day",
"limit": 50,
}
response = requests.get(f"{BASE_URL}/reddit/search", headers=headers, params=params)
result = response.json()
print(f"Found {result['total']} live mentions for '{result['keyword']}'")
for mention in result["data"]:
print(f" [{mention['found_in']}] r/{mention['subreddit']}: {mention['post_title']}")
Lister les monitors (Guzzle)
$client = new \GuzzleHttp\Client([
'base_uri' => 'https://pageradar.io/api/',
'headers' => [
'Authorization' => 'Bearer YOUR_TOKEN',
'Accept' => 'application/json',
],
]);
$response = $client->get('reddit/monitors');
$monitors = json_decode($response->getBody(), true)['data'];
foreach ($monitors as $monitor) {
echo "{$monitor['keyword']}: {$monitor['mentions_count']} mentions\n";
}
Rechercher des mentions avec filtres
$response = $client->get('reddit/mentions', [
'query' => [
'subreddit' => 'seo',
'sort' => 'score',
'date_from' => '2026-01-01',
'per_page' => 100,
],
]);
$data = json_decode($response->getBody(), true);
foreach ($data['data'] as $mention) {
echo "r/{$mention['subreddit']}: {$mention['post_title']} (score: {$mention['score']})\n";
}
echo "Total: {$data['meta']['total']} mentions\n";
Recherche en direct d'un mot-clé
$response = $client->get('reddit/search', [
'query' => [
'keyword' => 'your brand',
'time' => 'day',
'limit' => 50,
],
]);
$result = json_decode($response->getBody(), true);
echo "Found {$result['total']} live mentions for '{$result['keyword']}'\n";
foreach ($result['data'] as $mention) {
echo " [{$mention['found_in']}] r/{$mention['subreddit']}: {$mention['post_title']}\n";
}