Jump to section

API Documentation

This REST API reference covers URL inspection, batch checks, monitors, user-agent comparison, exports, billing, and account management. Most endpoints accept API keys and return JSON; public endpoints, user-session requirements, and other response formats are noted below. The full OpenAPI spec is available at /v1/openapi.yaml.

Base URL: https://api.checkredirects.io/v1

Authentication

For endpoints that accept API keys, send an Authorization header. Public endpoints need no key; Terms acceptance requires a direct user session instead.

Authorization: Bearer httpnd_your_api_key

Create API keys in Settings > API Keys in the web app, or via the API key endpoints. Keys use the httpnd_ prefix to distinguish them from session tokens.

Response Headers

Every authenticated API response includes monthly-capacity metadata. These headers do not describe the separate per-second request throttle; use GET /usage for your organization's effective runtime limits.

X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
X-Plan: pro
X-Monthly-Limit: 15000
X-Monthly-Used: 4218
X-Monthly-Remaining: 10782
X-Credits-Remaining: 5000
X-RateLimit-Limit: 15000
X-RateLimit-Remaining: 10782
X-RateLimit-Reset: 1711929600
HeaderDescription
X-Request-IDUnique ID for this request (useful for support)
X-PlanYour current plan: free, pro, or business
X-Monthly-LimitTotal checks allowed this month
X-Monthly-UsedChecks used so far this month
X-Monthly-RemainingChecks remaining this month
X-Credits-RemainingPurchased credit balance (used after monthly limit exhausted)
X-RateLimit-LimitCompatibility alias for X-Monthly-Limit; not the per-second request throttle
X-RateLimit-RemainingCompatibility alias for X-Monthly-Remaining
X-RateLimit-ResetCompatibility alias for the monthly reset timestamp
These headers appear on every authenticated response, not just inspection endpoints. You can build dashboards, alerts, or auto-scaling logic by reading these headers from any API call.

MCP Server

If you use Claude, Cursor, or another MCP-compatible AI assistant, you can skip the REST API entirely. The checkredirects MCP server lets your AI call redirect checking tools directly.

go install github.com/neondeerdatalabs/[email protected]

Available tools: check_url, inspect_url, compare_agents, batch_check_and_wait, create_monitor, export_to_sheets, batch_results, list_monitors.

Full setup instructions, config examples, and source code: MCP Server page | GitHub

Inspect a URL

POST /inspect

Inspect a single URL. Returns the full redirect chain with status codes, headers, timing, resolved IPs, and TLS certificate data at every hop.

Request

curl -X POST https://api.checkredirects.io/v1/inspect \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://flatfile.io",
    "method": "GET",
    "follow_redirects": true,
    "max_redirects": 10,
    "user_agent": "Googlebot/2.1",
    "headers": {"Accept-Language": "en-US"},
    "cookies": "session=abc123",
    "basic_auth": {"user": "admin", "pass": "secret"},
    "preset_id": "uuid-of-saved-preset"
  }'
FieldTypeRequiredDescription
urlstringYesURL to inspect (http/https only, max 2048 chars)
methodstringNoHEAD (default) or GET
follow_redirectsbooleanNoFollow redirects (default: true)
max_redirectsintegerNoMax hops to follow (default: 10, max: 20)
user_agentstringNoPreset key (e.g. googlebot_desktop) or raw UA string. See catalog
headersobjectNoCustom request headers (key-value pairs). Caller-supplied headers are sent only to the submitted origin and same-origin redirects.
cookiesstringNoCookie header value (e.g. "session=abc; token=xyz"). Sent only to the submitted origin and same-origin redirects.
basic_authobjectNoHTTP Basic Auth: {"user": "...", "pass": "..."}. Sent only to the submitted origin and same-origin redirects.
retrieve_bodybooleanNoParse final page for meta tags, canonical URL, OG tags (default: false). Paid plans only. Returns 403 upgrade_required on free plan.
preset_idstringNoDeprecated compatibility field; currently ignored. Send effective request options explicitly.

Response: success (redirect chain resolving to 200)

{
  "id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a",
  "original_url": "https://flatfile.io",
  "normalized_url": "https://flatfile.io/",
  "final_url": "https://flatfile.com/",
  "final_status": 200,
  "total_hops": 2,
  "total_time_ms": 342,
  "error": null,
  "hops": [
    {
      "hop_number": 1,
      "url": "https://flatfile.io/",
      "method": "GET",
      "status_code": 301,
      "status_message": "Moved Permanently",
      "resolved_ip": "76.76.21.21",
      "time_ms": 128,
      "headers": {
        "Location": ["https://flatfile.com/"],
        "Server": ["cloudflare"],
        "Cf-Ray": ["8a1b2c3d4e5f-IAD"]
      },
      "tls": {
        "version": "TLS 1.3",
        "cipher": "TLS_AES_128_GCM_SHA256",
        "certificate": {
          "subject": "flatfile.io",
          "issuer": "Let's Encrypt Authority X3",
          "not_before": "2025-11-01T00:00:00Z",
          "not_after": "2026-01-30T00:00:00Z",
          "san": ["flatfile.io", "www.flatfile.io"],
          "chain_valid": true
        }
      }
    },
    {
      "hop_number": 2,
      "url": "https://flatfile.com/",
      "method": "GET",
      "status_code": 200,
      "status_message": "OK",
      "resolved_ip": "76.76.21.22",
      "time_ms": 214,
      "headers": {
        "Content-Type": ["text/html; charset=utf-8"],
        "X-Frame-Options": ["DENY"],
        "Strict-Transport-Security": ["max-age=31536000"]
      },
      "tls": {
        "version": "TLS 1.3",
        "cipher": "TLS_AES_256_GCM_SHA384",
        "certificate": {
          "subject": "flatfile.com",
          "issuer": "DigiCert Inc",
          "not_before": "2025-06-15T00:00:00Z",
          "not_after": "2026-06-15T00:00:00Z",
          "san": ["flatfile.com", "*.flatfile.com"],
          "chain_valid": true
        }
      }
    }
  ]
}

Response: error (DNS failure)

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "original_url": "https://doesnt-exist-xyz.com",
  "normalized_url": "https://doesnt-exist-xyz.com/",
  "final_url": null,
  "final_status": null,
  "total_hops": 0,
  "total_time_ms": 45,
  "error": {
    "code": "dns_failure",
    "message": "DNS lookup failed: no such host"
  },
  "hops": []
}

Response fields

FieldTypeDescription
idstringUnique check ID (UUID)
original_urlstringURL as you submitted it
normalized_urlstringURL after normalization (trailing slash, lowercased host)
final_urlstring | nullURL that returned a non-redirect status, or null on error
final_statusinteger | nullHTTP status code of the final response, or null on error
total_hopsintegerNumber of hops in the redirect chain
total_time_msintegerTotal wall-clock time in milliseconds
errorobject | nullNull on success. See inspection errors
hopsarrayOrdered list of hops (empty on pre-connection errors)

Hop object

FieldTypeDescription
hop_numberinteger1-indexed position in the chain
urlstringURL requested for this hop
methodstringHTTP method used (HEAD or GET)
status_codeintegerHTTP status code
status_messagestringHTTP reason phrase (e.g. "Moved Permanently")
resolved_ipstringIP address the hostname resolved to
time_msintegerTime for this hop in milliseconds
headersobject or nullResponse headers, with array values. Stored and cached results omit cookies, authentication headers, and other credential-bearing headers. Fresh uncached inspections may return them to an authorized caller. Public shares return null.
tlsobject | nullTLS info (null for plain HTTP)
credentials_withheldbooleanTrue when caller-supplied credentials were withheld from this hop after an origin change or HTTPS downgrade.
credentials_withheld_reasonstring | nullorigin_change, scheme_downgrade, or unparsable_url; absent when credentials were not withheld.

TLS / certificate object

FieldTypeDescription
tls.versionstringTLS version (e.g. "TLS 1.3")
tls.cipherstringCipher suite used
tls.certificate.subjectstringCertificate subject (CN)
tls.certificate.issuerstringIssuing CA
tls.certificate.not_beforestringValidity start (ISO 8601)
tls.certificate.not_afterstringValidity end (ISO 8601)
tls.certificate.sanstring[]Subject Alternative Names
tls.certificate.chain_validbooleanWhether the full certificate chain validated

Watch a URL

POST /watch

Fire-and-forget single-URL inspection. The request returns immediately with 202 accepted; the inspection runs in the background and the result is POSTed to your webhook_url (HMAC-signed with the org's webhook secret. Same verification flow as batch webhooks). Useful when you don't want to hold an HTTP connection open or you're driving inspections from a serverless function.

Request

curl -X POST https://api.checkredirects.io/v1/watch \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "webhook_url": "https://your-server.com/webhook",
    "user_agent": "googlebot_desktop"
  }'
FieldTypeRequiredDescription
urlstringYesURL to inspect (http/https, max 2048 chars)
webhook_urlstringYesHTTPS URL to deliver the result to. Signed with X-Signature (HMAC-SHA256)
user_agentstringNoCatalog key or raw UA string

Response: success (202)

{
  "status": "accepted",
  "watch_id": "9a0b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
  "message": "URL check started. Results will be sent to your webhook."
}

Webhook payload (POST to your webhook_url)

{
  "event": "watch.completed",
  "watch_id": "9a0b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
  "url": "https://example.com",
  "status": "completed",
  "final_url": "https://example.com/",
  "final_status": 200,
  "total_hops": 1,
  "timestamp": "2026-06-16T18:30:00Z"
}

On failure, status is "failed" and an error_code field is set instead of final_url/final_status. See inspection errors for codes plus the two watch-specific codes below.

Billing

1 check unit is charged when the worker actually starts the inspection, not at request submission. That means:

  • A 202 accepted response does not guarantee a charge has been recorded yet.
  • If your capacity runs out between submission and worker pickup, the worker delivers a watch.completed webhook with status: "failed" and error_code: "insufficient_capacity". No inspection runs, no charge.
  • If the dispatcher itself can't accept more work, the HTTP call fails fast with 503 rate_limited and no webhook is sent.

Error responses

HTTPCodeWhen
422validation_errorMissing or invalid url / webhook_url (must be https://).
429rate_limitedPer-second API rate limit, or zero capacity available at submission time.
503rate_limitedWatch dispatcher is at capacity. Retry shortly.

Batch Inspect

POST /batch

Submit multiple URLs for asynchronous processing. The same request configuration (method, headers, user agent, etc.) is applied to all URLs in the batch.

Request

curl -X POST https://api.checkredirects.io/v1/batch \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": ["https://example.com", "https://flatfile.io"],
    "method": "GET",
    "user_agent": "Googlebot/2.1",
    "cookies": "session=abc",
    "basic_auth": {"user": "admin", "pass": "secret"}
  }'
FieldTypeRequiredDescription
urlsstring[]YesURLs to inspect. Use GET /usage to read the organization's effective maximum; it may differ from the standard plan default.
methodstringNoHEAD (default) or GET
follow_redirectsbooleanNoFollow redirects (default: true)
max_redirectsintegerNoMax hops (default: 10, max: 20)
user_agentstringNoCustom User-Agent
headersobjectNoCustom headers for each submitted URL's origin and same-origin redirects; withheld on HTTPS-to-HTTP downgrades.
cookiesstringNoCookie header value, with the same origin and downgrade restrictions.
basic_authobjectNoHTTP Basic Auth, with the same origin and downgrade restrictions.
retrieve_bodybooleanNoParse final page for meta tags (default: false). Paid plans only; Free returns 403 upgrade_required.
preset_idstringNoAssociates an organization-owned preset for metadata only. Send effective request options explicitly.
webhook_urlstringNoURL for a batch completion notification. Publicly reachable HTTPS destination; all plans. See webhooks.

Response

{
  "job_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "status": "pending",
  "total_urls": 2
}

Batch job lifecycle

Jobs progress through these states:
pending → running → completed

A job is completed when all URLs have been processed, even if some individual URLs failed. There is no separate "partially_failed" status. Check failed_urls in the progress response to see how many had errors. Individual check results will have error set to null (success) or an error object (failure).

GET /batch/{id}/progress

Poll for batch completion. This is a lightweight endpoint. Call it every 1-2 seconds.

curl https://api.checkredirects.io/v1/batch/JOB_ID/progress \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "status": "running",
  "total_urls": 50,
  "completed_urls": 32,
  "failed_urls": 1
}
FieldTypeDescription
statusstringpending, running, or completed
total_urlsintegerTotal URLs in the batch
completed_urlsintegerURLs that completed successfully
failed_urlsintegerURLs that had errors (DNS failure, timeout, etc.)
You can fetch results while the job is still running. You'll get whatever has completed so far. No need to wait for completed status.

GET /batch/{id}

Get paginated results. Each item in checks has the same shape as a single inspect response.

curl "https://api.checkredirects.io/v1/batch/JOB_ID?page=1&per_page=25" \
  -H "Authorization: Bearer httpnd_your_key"

Query parameters

ParamTypeDefaultDescription
pageinteger1Page number (1-indexed)
per_pageinteger50Results per page (max 100)
statusstring—Filter by status codes, comma-separated (e.g. 301,404)
errorboolean—Set to true to show only failed checks
sortstring—Sort by: latency, status, hops
orderstringascasc or desc

Response

{
  "job_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "status": "completed",
  "total_urls": 50,
  "completed_urls": 48,
  "failed_urls": 2,
  "page": 1,
  "per_page": 25,
  "total_count": 50,
  "checks": [
    {
      "id": "...",
      "original_url": "https://example.com",
      "normalized_url": "https://example.com/",
      "final_url": "https://example.com/",
      "final_status": 200,
      "total_hops": 1,
      "total_time_ms": 89,
      "error": null,
      "hops": [...]
    }
  ]
}
FieldTypeDescription
job_idstringBatch job ID
statusstringJob status
total_urlsintegerTotal URLs submitted
completed_urlsintegerSuccessfully completed
failed_urlsintegerFailed with errors
pageintegerCurrent page number
per_pageintegerResults per page
total_countintegerTotal results matching your filters
checksarrayArray of inspect response objects

GET /batch/{id}/export/csv

Download batch results as a CSV file. Returns Content-Type: text/csv with a Content-Disposition header.

curl -O -J "https://api.checkredirects.io/v1/batch/JOB_ID/export/csv" \
  -H "Authorization: Bearer httpnd_your_key"

POST /batch/{id}/export/sheets

Export batch results to a connected Google Sheets account. Requires a Google Sheets connection.

curl -X POST "https://api.checkredirects.io/v1/batch/JOB_ID/export/sheets" \
  -H "Authorization: Bearer httpnd_your_key"

User-Agent Catalog

GET /user-agents

Returns the full catalog of available user-agent presets and comparison packs. Public endpoint. No auth required.

curl https://api.checkredirects.io/v1/user-agents

The response includes two arrays: agents (individual user-agents with keys, labels, groups, and full strings) and packs (preset groups for comparison mode).

Using preset keys

Anywhere the API accepts a user_agent field, you can pass either a preset key (e.g. googlebot_desktop) or a raw UA string. If it matches a known key, the full string is used. Otherwise it's treated as a literal.

// Using a preset key
{"url": "https://example.com", "user_agent": "googlebot_desktop"}

// Using a raw string
{"url": "https://example.com", "user_agent": "MyCustomBot/1.0"}

Available groups

GroupExample keys
Search Engine Crawlersgooglebot_desktop, googlebot_mobile, bingbot_desktop, yandexbot
Social Media Crawlersfacebook, twitterbot, linkedin, slackbot, discord
Desktop Browserschrome_windows, chrome_mac, firefox_windows, safari_mac, edge_windows
Mobile Browserschrome_android, safari_iphone, safari_ipad, samsung_browser
AI Crawlersgptbot, chatgpt_user, claudebot, google_extended, bytespider
Utilitycurl, wget, empty

User-Agent Comparison

POST /compare-agents

Inspect a single URL with multiple user-agents simultaneously and compare the responses. Flags whether the responses differ (different status code or final URL). Available on all plans. Each user-agent counts as one check.

With explicit user-agents

curl -X POST https://api.checkredirects.io/v1/compare-agents \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "user_agents": ["googlebot_desktop", "chrome_windows", "facebook"]}'

With a preset pack

curl -X POST https://api.checkredirects.io/v1/compare-agents \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "pack": "seo_essentials"}'
FieldTypeRequiredDescription
urlstringYesURL to inspect
user_agentsstring[]*List of UA keys or raw strings (max 10)
packstring*Preset pack key (alternative to user_agents)
methodstringNoHEAD or GET (default: GET)
max_redirectsintegerNoMaximum redirects to follow (default: 10, max: 20)
follow_redirectsbooleanNoFollow redirects (default: true)
retrieve_bodybooleanNoParse final page for meta tags (default: false). Paid plans only; Free returns 403 upgrade_required.

Provide either user_agents or pack, not both.

Available packs

KeyNameAgentsUse case
seo_essentialsSEO Essentials5Do search engines see the same thing as browsers?
social_previewSocial Preview Check6Will shared links look right on every platform?
mobile_vs_desktopMobile vs Desktop6Catch m-dot redirects and device-based routing
ai_crawler_auditAI Crawler Audit6Are your AI blocking rules actually working?
full_coverageFull Coverage10Check everything. Search, browsers, social, AI

Response

{
  "url": "https://example.com",
  "differ": true,
  "results": [
    {
      "user_agent_key": "googlebot_desktop",
      "user_agent_label": "Googlebot Desktop",
      "user_agent_string": "Mozilla/5.0 ...",
      "final_url": "https://example.com/",
      "final_status": 200,
      "total_hops": 1,
      "total_time_ms": 142,
      "error": null,
      "hops": [...]
    },
    {
      "user_agent_key": "safari_iphone",
      "user_agent_label": "Safari (iPhone)",
      "user_agent_string": "Mozilla/5.0 (iPhone; ...",
      "final_url": "https://m.example.com/",
      "final_status": 200,
      "total_hops": 2,
      "total_time_ms": 186,
      "error": null,
      "hops": [...]
    }
  ]
}

Monitors

Monitors run a saved batch on a recurring cadence (minimum every 30 minutes), POST results to your webhook on each run, and optionally append each run to a Google Spreadsheet. They auto-pause after 5 consecutive failed runs. Use reset-failures to clear the counter. Paid plans only.

GET /monitors

List all monitors in the organization.

curl "https://api.checkredirects.io/v1/monitors?page=1&per_page=20" \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "data": [{...monitor...}],
  "total": 4,
  "page": 1,
  "per_page": 20
}

POST /monitors

Create a new monitor. Each URL counts toward the per-monitor URL cap (see plan limits). Plan caps the number of monitors per org (Pro: 50, Business: 250).

curl -X POST https://api.checkredirects.io/v1/monitors \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Homepage canary",
    "urls": ["https://example.com", "https://example.com/about"],
    "interval_minutes": 60,
    "method": "GET",
    "user_agent": "googlebot_desktop",
    "webhook_url": "https://your-server.com/monitor-webhook",
    "sheets_append": false
  }'
FieldTypeRequiredDescription
namestringYesDisplay name for the monitor
urlsstring[]*Simple list of URLs. Use either urls or items, not both.
itemsobject[]*Alternative to urls with per-URL passthrough data: [{"url": "...", "data": {...}}]. The data object is included in every run's results and webhook payloads. Max 64KB per URL.
interval_minutesintegerYesHow often to run. Minimum 30.
preset_idstringNoAssociates an organization-owned preset for metadata only. Send effective request options explicitly.
methodstringNoHEAD (default) or GET
follow_redirectsbooleanNoFollow redirects (default true)
max_redirectsintegerNoMax hops (default 10, max 20)
user_agentstringNoCatalog key or raw UA string
headersobjectNoCustom request headers
cookiesstringNoCookie header value
webhook_urlstringNoHTTPS URL to POST each run's result to. Signed identically to batch webhooks.
sheets_appendbooleanNoIf true, auto-create a spreadsheet on the first run and append each subsequent run. Requires a connected Google Sheets account.

Monitor object

{
  "id": "uuid",
  "name": "Homepage canary",
  "urls": ["https://example.com"],
  "interval_minutes": 60,
  "request_config": {"method": "GET", "user_agent": "..."},
  "has_credentials": {"headers": true, "cookies": false},
  "status": "active",
  "webhook_url": "https://your-server.com/monitor-webhook",
  "sheets_append": false,
  "sheets_spreadsheet_id": null,
  "last_run_at": "2026-06-16T18:00:00Z",
  "next_run_at": "2026-06-16T19:00:00Z",
  "last_job_id": "job-uuid",
  "consecutive_failures": 0,
  "badge_url": "https://api.checkredirects.io/v1/badge/uuid/SIGNED",
  "created_by": "user-uuid",
  "created_at": "2026-06-01T00:00:00Z",
  "updated_at": "2026-06-16T18:00:00Z"
}
FieldNotes
statusactive, paused, or deleted
consecutive_failuresResets to 0 on success. Monitor auto-pauses at 5.
badge_urlPre-signed public URL for an embeddable SVG status badge. See Status Badge. Omitted when the server has no badge-signing secret configured.
sheets_spreadsheet_idThe auto-created spreadsheet ID after the first sheets-append run.
request_configOperational request settings only. Stored header and cookie values are write-only and never returned.
has_credentialsBoolean markers showing whether supported write-only credential kinds are configured, without exposing their values.

GET /monitors/{id}

Get a single monitor by ID.

PUT /monitors/{id}

Full operational update. name and interval_minutes are required. Omit preset_id to clear its metadata-only association. Omit headers or cookies to preserve the stored write-only value; send an empty object or empty string to clear it.

DELETE /monitors/{id}

Soft-delete a monitor. Admin role required.

{"status": "deleted"}

POST /monitors/{id}/pause

Pause scheduling. The monitor will not run again until resumed.

{"status": "paused"}

POST /monitors/{id}/resume

Resume a paused monitor. The next run is scheduled on the next scheduler tick (within ~1 minute).

{"status": "active"}

POST /monitors/{id}/trigger

Manually trigger an immediate run. The HTTP call returns 200 as soon as the run is scheduled; billing happens when the scheduler executes the run. If there is no capacity at that point, the scheduler logs a skip. The trigger endpoint itself still returns 200.

{"status": "triggered"}

POST /monitors/{id}/reset-failures

Clears consecutive_failures and any degraded badge state. Use after fixing whatever was breaking the monitor so the next run starts from a clean slate. Does not delete the run history. Member role required.

{"status": "reset"}

GET /monitors/{id}/runs

List the batch jobs spawned by this monitor, newest first.

curl "https://api.checkredirects.io/v1/monitors/MONITOR_ID/runs?page=1&per_page=20" \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "data": [
    {
      "job_id": "job-uuid",
      "status": "completed",
      "total_urls": 2,
      "completed_urls": 2,
      "failed_urls": 0,
      "created_at": "2026-06-16T18:00:00Z",
      "completed_at": "2026-06-16T18:00:02Z"
    }
  ],
  "total": 137,
  "page": 1,
  "per_page": 20
}

To read the per-URL results for a run, pass job_id to GET /batch/{id}.

Monitor Bulk Operations

Three bulk endpoints share the same response shape and per-item error reporting. Useful for migrating a list of URLs into monitors in one round-trip, or pausing every monitor before a planned maintenance window.

Bulk response shape

{
  "total_requested": 3,
  "succeeded": 2,
  "failed": 1,
  "results": [
    {"index": 0, "status": "created", "monitor": {...monitor...}},
    {"index": 1, "status": "created", "monitor": {...monitor...}},
    {"index": 2, "status": "error", "error": {"code": "validation_error", "message": "interval_minutes must be at least 30"}}
  ]
}

For pause/resume, each results[] entry has id (the monitor UUID) instead of index, and successful items use "status": "paused" or "resumed" instead of "created". The whole call returns 200 even when individual items fail. Check failed and per-item error objects. Each bulk call accepts a maximum of 50 IDs (pause/resume) or 25 monitor bodies (create).

POST /monitors/bulk

Create up to 25 monitors in one call. Each item uses the same body as POST /monitors. Plan-level monitor caps still apply. The operation rejects the whole batch with 403 upgrade_required if accepting the new monitors would exceed your plan limit.

curl -X POST https://api.checkredirects.io/v1/monitors/bulk \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "monitors": [
      {"name": "/about", "urls": ["https://example.com/about"], "interval_minutes": 60},
      {"name": "/pricing", "urls": ["https://example.com/pricing"], "interval_minutes": 60}
    ]
  }'

POST /monitors/bulk/pause

Pause multiple monitors by ID.

curl -X POST https://api.checkredirects.io/v1/monitors/bulk/pause \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["monitor-uuid-1", "monitor-uuid-2"]}'

POST /monitors/bulk/resume

Resume multiple monitors by ID. Same request shape as bulk pause.

Status Badge

GET /badge/{id}/{sig}

Returns a public SVG badge showing the monitor's current status. Suitable for embedding in a README, status page, or runbook. No authentication required; the URL is HMAC-signed so knowing a monitor UUID alone isn't enough to query it.

Use the badge_url field from any monitor response. The server emits it pre-signed for the correct host (the API origin, e.g. https://api.checkredirects.io, not the app origin). Requests without a valid signature render a generic "invalid signature" SVG (not a 404) so a broken README image still tells you what went wrong.

Example embed:

<img src="https://api.checkredirects.io/v1/badge/MONITOR_ID/SIGNATURE"
     alt="Homepage canary status">
The badge sets Cache-Control: public, max-age=300, s-maxage=300. Expect up to ~5 minutes of staleness in embedded images after a status change.

Sitemap Import

GET /sitemap/parse?url=...

Fetches and parses an XML sitemap, returning the extracted URLs. Supports both <urlset> and <sitemapindex> formats. Use the returned URLs as input to the batch endpoint.

curl "https://api.checkredirects.io/v1/sitemap/parse?url=https://example.com/sitemap.xml" \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "url": "https://example.com/sitemap.xml",
  "url_count": 47,
  "urls": [
    "https://example.com/",
    "https://example.com/about",
    "https://example.com/pricing",
    ...
  ]
}
Returns at most 1,000 URLs. For sitemap indexes, child sitemaps are fetched automatically. The sitemap URL is SSRF-validated. Private/internal IPs are blocked.

Job History

GET /jobs

Returns your organization's inspection job history, newest first. For single-URL jobs, includes the first check's URL and final status for display.

curl "https://api.checkredirects.io/v1/jobs?page=1&per_page=20" \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "jobs": [
    {
      "id": "job-uuid",
      "job_type": "single",
      "status": "completed",
      "total_urls": 1,
      "completed_urls": 1,
      "failed_urls": 0,
      "created_at": "2026-03-23T10:30:00Z",
      "first_url": "https://flatfile.io",
      "final_status": 200,
      "first_check_id": "check-uuid"
    },
    {
      "id": "job-uuid-2",
      "job_type": "batch",
      "status": "completed",
      "total_urls": 50,
      "completed_urls": 48,
      "failed_urls": 2,
      "created_at": "2026-03-22T14:00:00Z"
    }
  ],
  "page": 1,
  "per_page": 20,
  "total_count": 42
}

Job Comparison (Diff)

GET /jobs/{id}/compare/{other_id}

Compare two batch jobs side-by-side. Matches checks by original_url and flags changes in status code, final URL, or hop count. Useful for tracking migration progress or monitoring changes over time.

curl "https://api.checkredirects.io/v1/jobs/JOB_A_ID/compare/JOB_B_ID" \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "job_a": "job-a-uuid",
  "job_b": "job-b-uuid",
  "summary": {
    "total_urls": 50,
    "changed": 3,
    "unchanged": 45,
    "added_in_b": 1,
    "removed_from_a": 1
  },
  "diffs": [
    {
      "url": "https://example.com/old-page",
      "change": "changed",
      "status_a": 301,
      "status_b": 200,
      "final_url_a": "https://example.com/new-page",
      "final_url_b": "https://example.com/old-page",
      "hops_a": 2,
      "hops_b": 1,
      "time_ms_a": 245,
      "time_ms_b": 89
    },
    {
      "url": "https://example.com/removed",
      "change": "removed",
      "status_a": 200,
      "final_url_a": "https://example.com/removed"
    }
  ]
}
Change valueMeaning
unchangedSame status, final URL, and hop count
changedStatus code, final URL, or hop count changed
addedURL exists in job B but not job A
removedURL exists in job A but not job B

Shareable Results

GET /results/{check_id}

Retrieve a shared inspection result by check ID without authentication. The containing job must be explicitly shared; private, revoked, expired, or missing results return 404.

curl https://api.checkredirects.io/v1/results/CHECK_UUID

The web app uses app.checkredirects.io/r/{check_id}. Both this endpoint and public batch results return a reduced inspection response:

  • URL fields and redirect-chain entries omit user information, query strings, and fragments. Paths remain visible.
  • Each check omits job_id and passthrough_data. Hop headers and tls are null; resolved_ip, geo, and meta are omitted.
  • Hop dns_ms, connect_ms, tls_ms, and ttfb_ms are zero. Total duration and hop time_ms remain.
  • Hop status_message is empty. Inspection error codes remain, but their messages are generic.

Authenticated result reads, CSV exports, and Sheets exports do not use this public-share redaction. Review exported URLs and metadata before forwarding them.

Result Retention

Saved results, including public shares, use the organization's plan when the job was created. Later plan changes do not change that job's retention periods. Periods run from job creation.

PlanRedirect-hop detailResult summaries and jobs
Free7 days30 days
Pro30 days90 days
Business90 days6 months

Scheduled cleanup removes expired data from completed, failed, and cancelled jobs. Hop detail can expire before the summary, leaving an empty hops array. Once the result or job is deleted, its read and share URLs return 404. Sharing does not extend retention; export results you need to keep.

Batch Webhooks

When submitting a batch job, include a webhook_url to receive a POST notification when the batch completes. Available on every plan. The destination must be a publicly reachable HTTPS URL; invalid or prohibited destinations return 422 validation_error.

// Submit batch with webhook
curl -X POST https://api.checkredirects.io/v1/batch \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com"], "webhook_url": "https://your-server.com/webhook"}'

Webhook payload (POST to your URL)

{
  "event": "batch.completed",
  "job_id": "job-uuid",
  "status": "completed",
  "total_urls": 50,
  "completed_urls": 48,
  "failed_urls": 2,
  "timestamp": "2026-03-23T10:35:00Z"
}

Verifying webhook signatures

Each webhook includes an X-Signature header containing an HMAC-SHA256 signature of the request body. The full webhook signing secret is delivered once by POST /v1/org/webhook-secret/rotate (admin role required) and shown once in Settings. GET /v1/org/webhook-secret only returns a non-revealing preview (first 8 chars + length) so the value cannot be exfiltrated by a stolen viewer-level session. If you lose the secret you have to rotate to get a new one. Rotation immediately invalidates the previous secret. Any in-flight webhooks signed with the old secret will fail verification. Pause monitors before rotating if you need zero missed deliveries.

// Node.js verification example
const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
const isValid = verifyWebhook(rawBody, req.headers['x-signature'], WEBHOOK_SECRET);
# Python verification example
import hmac, hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
Webhooks are retried once on failure (2-second delay). The request has a 10-second timeout and includes User-Agent: checkredirects.io/webhook and X-Signature headers.

Delivery status and errors

Authenticated GET /batch/{id} responses include webhook once a delivery result is recorded. Its status is delivered or failed. The optional delivered_at timestamp records the outcome, including a failed attempt; it does not by itself mean delivery succeeded.

// Webhook field within a batch response
"webhook": {
  "status": "failed",
  "delivered_at": "2026-09-26T12:00:00Z",
  "error": "webhook_http_error_503"
}

webhook.error is an optional diagnostic code, not a receiver URL or raw transport error. Check status even when error is absent.

CodeMeaning
webhook_http_error_<status>The receiver returned an unsuccessful HTTP status, such as webhook_http_error_503.
webhook_timeout, webhook_network_errorDelivery timed out or the receiver could not be reached.
webhook_url_invalid, webhook_redirect_rejectedThe destination was rejected or attempted a redirect.
webhook_payload_error, webhook_signing_error, webhook_request_errorThe payload, signature, or request could not be prepared.
webhook_canceled, webhook_delivery_errorDelivery was cancelled or failed for another reason.

Batch Sharing

Turn a completed batch into a public, no-auth-required link. Useful for sharing migration reports with clients or embedding QA results in a ticket.

POST /batch/{id}/share

Mark the batch as publicly accessible at /b/{id}. Member role required.

curl -X POST https://api.checkredirects.io/v1/batch/JOB_ID/share \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "is_public": true,
  "share_url": "https://app.checkredirects.io/b/JOB_ID"
}

DELETE /batch/{id}/share

Revoke public access. The link returns 404 to unauthenticated callers immediately.

curl -X DELETE https://api.checkredirects.io/v1/batch/JOB_ID/share \
  -H "Authorization: Bearer httpnd_your_key"

Response

{"is_public": false}

GET /batch/{id}/public

Read a shared batch without authentication. Returns batch counts and paginated checks using the public-share redaction; webhook diagnostics and batch statistics are omitted. Returns 404 when the batch is private, revoked, expired, or missing. Result retention still applies.

curl "https://api.checkredirects.io/v1/batch/JOB_ID/public?page=1&per_page=25"

Webhook Secrets

Manage the signing secret used to verify webhook payloads. See Batch Webhooks for verification examples.

GET /org/webhook-secret

Returns a preview of the current webhook signing secret. The first 8 characters plus the total length. The full value is never returned by this endpoint; rotate to receive a fresh secret in plaintext. Admin role required.

curl https://api.checkredirects.io/v1/org/webhook-secret \
  -H "Authorization: Bearer httpnd_your_key"

Response

{"preview": "a1b2c3d4", "length": 64}

POST /org/webhook-secret/rotate

Generate a new webhook signing secret. The previous secret is immediately invalidated. The full value is returned once; store it before navigating away. Admin role required.

curl -X POST https://api.checkredirects.io/v1/org/webhook-secret/rotate \
  -H "Authorization: Bearer httpnd_your_key"

Response

{"webhook_secret": "e5f6a7b8..."}

Test a Webhook

POST /webhook-test

Sends a signed test payload to a URL so you can verify your signature-verification implementation before pointing real traffic at it. The request is HMAC-signed with your org's current webhook secret. Member role required.

curl -X POST https://api.checkredirects.io/v1/webhook-test \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-server.com/webhook"}'
FieldTypeRequiredDescription
urlstringYesHTTPS URL to deliver the test payload to. SSRF-validated (private/internal IPs rejected).

Response: success

{"status": "sent", "message": "Test webhook delivered successfully."}

Error responses

HTTPCodeWhen
422validation_errorurl is missing, not HTTPS, or fails SSRF validation.
502webhook_failedThe remote endpoint did not respond with a 2xx status.

MCP Ping

POST /mcp/ping

Connectivity probe used by the MCP server to confirm the API is reachable. Any valid bearer token works (viewer role and up).

curl -X POST https://api.checkredirects.io/v1/mcp/ping \
  -H "Authorization: Bearer httpnd_your_key"

Response

{"status": "ok"}

Presets

Presets save non-sensitive request configurations (method, user agent, and redirect behavior) for reuse in the web app. The API accepts only the four documented fields; headers, cookies, authentication data, and arbitrary fields are rejected. A preset_id on batch and monitor requests records which saved configuration was selected, but API clients must send the effective request options explicitly. Requires a paid plan.

GET /presets

List all presets for your organization.

curl https://api.checkredirects.io/v1/presets \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "data": [
    {
      "id": "c3d4e5f6-a7b8-9012-cdef-234567890abc",
      "name": "Googlebot",
      "description": "Inspect as Googlebot with US English",
      "config": {
        "method": "GET",
        "user_agent": "Googlebot/2.1 (+http://www.google.com/bot.html)",
        "follow_redirects": true,
        "max_redirects": 10
      },
      "is_default": false,
      "created_by": "user-uuid",
      "created_at": "2026-01-15T10:30:00Z",
      "updated_at": "2026-01-15T10:30:00Z"
    }
  ],
  "total": 1
}

POST /presets

Create a new preset.

curl -X POST https://api.checkredirects.io/v1/presets \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Googlebot",
    "description": "Inspect as Googlebot with US English",
    "config": {
      "method": "GET",
      "user_agent": "Googlebot/2.1 (+http://www.google.com/bot.html)",
      "follow_redirects": true,
      "max_redirects": 10
    }
  }'
FieldTypeRequiredDescription
namestringYesPreset name
descriptionstringNoOptional description
configobjectNoClosed request config: method, user_agent, follow_redirects, and max_redirects only

GET /presets/{id}

Get a single preset by ID.

PUT /presets/{id}

Update a preset. Same request body as POST. Members can only edit their own presets; admins can edit any.

DELETE /presets/{id}

Delete a preset. Requires admin role.

Usage

GET /usage

Returns your organization's current usage and effective runtime limits. Numeric admin overrides are organization-wide, so API keys and browser sessions for the same organization share these values.

curl https://api.checkredirects.io/v1/usage \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "monthly_limit": 15000,
  "used_this_month": 4218,
  "used_today": 142,
  "plan": "pro",
  "check_credits": 5000,
  "reward_credits": 0,
  "effective_limits": {
    "monthly_checks": 15000,
    "batch_size": 200,
    "max_inflight_urls": 500,
    "max_concurrent_batches": 3,
    "api_requests_per_sec": 25,
    "max_monitors": 50
  }
}

batch_size is the static maximum for a new batch after the organization, server, and in-flight thresholds are reconciled. Work already in flight can temporarily reduce immediate capacity, and concurrent submissions can briefly pass the in-flight URL or concurrent-batch thresholds, so callers must still handle 429 responses. api_requests_per_sec is an organization-shared one-second sliding-window throttle on protected execution routes, not a guaranteed per-minute allowance. max_monitors includes plan entitlement; a value of zero means monitor creation is unavailable.

GET /usage/history

Returns daily usage for the last 90 days. Requires admin role.

curl https://api.checkredirects.io/v1/usage/history \
  -H "Authorization: Bearer httpnd_your_key"

Response

[
  {"date": "2026-03-23", "checks_used": 142},
  {"date": "2026-03-22", "checks_used": 87},
  ...
]

Billing

Billing endpoints manage Stripe subscriptions and credit purchases. These are typically used from the web app, but are available via the API. Owner role required.

GET /billing/status

Returns your current plan, subscription status, and credit balance.

curl https://api.checkredirects.io/v1/billing/status \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "plan": "pro",
  "stripe_subscription_id": "sub_...",
  "period_end": "2026-04-15T00:00:00Z",
  "check_credits": 5000
}

POST /billing/checkout

Start a plan change for your organization. Redirect the user to redirect_url.

If your organization has no active subscription, action is "checkout" and redirect_url is a Stripe-hosted checkout page. If your organization already has an active subscription, action is "updated": the plan is changed in place on the existing subscription (proration is added to the next invoice by default) and no new Stripe session is created.

curl -X POST https://api.checkredirects.io/v1/billing/checkout \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"plan": "pro"}'

Response

{"action": "checkout", "redirect_url": "https://checkout.stripe.com/..."}

POST /billing/credits

Purchase a credit pack (10,000 checks for $10). Credits are used when your monthly limit is exhausted. In a Free organization, purchased credits expire after two years without being added or used; they do not expire while the organization is on a paid plan.

curl -X POST https://api.checkredirects.io/v1/billing/credits \
  -H "Authorization: Bearer httpnd_your_key"

Response

{"checkout_url": "https://checkout.stripe.com/..."}

POST /billing/portal

Creates a Stripe Customer Portal session to manage your subscription, payment method, and invoices.

Response

{"portal_url": "https://billing.stripe.com/..."}

Organization & Team

Manage your organization settings and team members. Team management requires a paid plan.

GET /org

Get organization details.

{
  "id": "org-uuid",
  "name": "Acme Corp",
  "slug": "acme-corp",
  "plan": "pro",
  "support_access_enabled": false
}

PUT /org

Update organization name. Requires admin role.

{"name": "New Name"}

GET /org/members

List all team members with their roles.

[
  {"id": "user-uuid", "email": "[email protected]", "name": "Alice", "role": "owner"},
  {"id": "user-uuid", "email": "[email protected]", "name": "Bob", "role": "member"}
]

POST /org/invite

Invite a new member. Requires admin role + paid plan.

{"email": "[email protected]", "role": "member"}

PUT /org/members/{user_id}/role

Change a member's role. Requires admin role + paid plan.

{"role": "admin"}

DELETE /org/members/{user_id}

Remove a member from the organization. Requires admin role + paid plan.

Roles

RoleInspectBatchManage TeamManage BillingAdmin Settings
OwnerYesYesYesYesYes
AdminYesYesYesNoYes
MemberYesYesNoNoNo
ViewerRead-onlyRead-onlyNoNoNo

Org Settings

Toggles that change how the org is treated by the platform. All require admin (or owner, where noted) and are blocked while you're being impersonated by support.

PUT /org/support-access

Allow or revoke support access for troubleshooting, including support impersonation. Grants expire after 48 hours. Owner role required.

curl -X PUT https://api.checkredirects.io/v1/org/support-access \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

PUT /org/data-opt-out

Control optional organization data collection. Available on every plan; admin role required. The opt_out boolean is required. GET /org also reports dpa_restricted; when true, setting opt_out:false returns 409 dpa_collection_restricted. Enabling opt-out remains allowed.

{"opt_out": true}

Response

{"opt_out": true}

PUT /org/auto-export-sheets

When enabled, every inspection and batch automatically appends to the org's default Google Spreadsheet. Requires a connected Google Sheets account. Paid plans only; admin role required.

{"enabled": true}

Response

{"auto_export_sheets": true}

DELETE /org

Owner only. Stops organization work, cancels the recorded subscription, and verifies billing before permanently deleting the organization's primary application records and stored credentials. This cannot be undone. It does not automatically erase sign-in accounts, provider-held records, or backups; see deletion boundaries in the Privacy Policy.

{"status": "deleted"}
ResponseWhat to do
409 billing_out_of_syncNo application data was deleted. Follow the response's retry or support guidance for the billing conflict.
502 billing_unavailableBilling cancellation or verification failed; no application data was deleted. Retry or contact support.
503 organization_drain_timeout, organization_unavailable, or billing_disabledDeletion could not proceed. Retry later.
503 deletion_outcome_unavailableDeletion may have completed. Contact support before retrying.

Subscription cancellation may succeed even if a later deletion step fails. A timeout or 500 response is not confirmation that data remains or that deletion succeeded; contact support if the outcome is unclear.

Terms Acceptance

API keys can read organization acceptance status, but cannot accept Terms on a person's behalf. Acceptance requires an explicit action by a directly signed-in organization owner. Impersonation and platform-admin sessions cannot accept.

EndpointPurpose
GET /terms/currentPublic metadata: current version, content_hash, acceptance_enabled, signup_required, update_summary, and notice_hash.
GET /terms/versions/{version}Public, exact published document text. Unpublished versions return 404.
GET /org/termsOrganization status, including accepted_at, notice_displayed_at, and can_accept. Viewer role or higher.
POST /org/terms/acceptDirect owner session only. Send accepted_terms:true, terms_version, and terms_content_hash for the document shown. Repeating acceptance preserves the first timestamp.
POST /org/terms/noticeDirect owner session only. Record display of the notice using terms_version, terms_content_hash, and notice_hash. This is not acceptance.

When acceptance is enabled, signup and invitation acceptance include accepted_terms, terms_version, and terms_content_hash from the document shown. Server-side acceptance is required when signup_required is true. Invitation acceptance records the individual's agreement, not an owner's agreement for the organization. Link the Privacy Policy separately.

On 409 terms_changed or terms_disabled, refresh metadata and the consent screen; never substitute a new version and resubmit acceptance automatically. These APIs do not themselves suspend existing API keys or establish legal effective dates. See the OpenAPI request and response schemas for full details.

API Key Management

Manage API keys programmatically. Requires admin role.

GET /api-keys

List all API keys for your organization. The key value is not included. It's only shown once at creation.

[
  {
    "id": "key-uuid",
    "name": "CI Pipeline",
    "prefix": "httpnd_a1b2",
    "created_by": "user-uuid",
    "created_at": "2026-01-10T08:00:00Z",
    "last_used_at": "2026-03-22T14:30:00Z",
    "expires_at": null
  }
]

POST /api-keys

Create a new API key. The full key is returned only in this response. Store it securely.

curl -X POST https://api.checkredirects.io/v1/api-keys \
  -H "Authorization: Bearer httpnd_your_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "CI Pipeline"}'

Response

{
  "id": "key-uuid",
  "name": "CI Pipeline",
  "key": "httpnd_a1b2c3d4e5f6g7h8i9j0..."
}

DELETE /api-keys/{id}

Revoke an API key immediately.

Rewards

One-shot actions that grant reward credits when completed (e.g. running your first batch or connecting Google Sheets). Reward credits are used before your monthly allowance and purchased credits, and expire after 30 days without being added or used.

GET /rewards

List every reward and whether your org has claimed it. Viewer role or above.

curl https://api.checkredirects.io/v1/rewards \
  -H "Authorization: Bearer httpnd_your_key"

Response

{
  "rewards": [
    {
      "key": "try_batch",
      "title": "Run your first batch",
      "description": "Run a batch check and we'll cover it plus 10 bonus credits.",
      "credits": 10,
      "max_claims": 1,
      "claimed": true,
      "claim_count": 1
    },
    {
      "key": "connect_sheets",
      "title": "Connect Google Sheets",
      "description": "Connect your Google Sheets account and earn 25 credits.",
      "credits": 25,
      "max_claims": 1,
      "claimed": false,
      "claim_count": 0
    }
  ],
  "total_unclaimed_credits": 25
}

Reward items may include require_plan (e.g. "pro") when the reward is plan-gated. Some rewards (like try_batch) calculate credits at claim time and may award the listed base plus a dynamic bonus.

Reward keys

KeyWhat unlocks it
try_batchRun a batch check (dynamic: covers the batch plus 10 bonus credits)
try_compare_agentsRun a user-agent comparison (Pro+; dynamic: covers the comparison plus 50 bonus credits)
share_xShare checkredirects.io on X
share_linkedinShare checkredirects.io on LinkedIn
connect_sheetsConnect a Google Sheets account
create_api_keyCreate your first API key
use_mcpMake a request via the MCP server
invite_memberInvite a teammate (Pro+)

POST /rewards/{key}/claim

Claim a reward by its action key. For most rewards the API verifies the underlying action has actually happened (e.g. You can't claim try_batch without having run a batch). Idempotent: re-claiming a one-shot reward returns 422 already_claimed instead of double-granting credits. Member role required.

curl -X POST https://api.checkredirects.io/v1/rewards/try_batch/claim \
  -H "Authorization: Bearer httpnd_your_key"

Response: success

{
  "credits_awarded": 10,
  "new_credit_balance": 5010,
  "message": "You earned 10 credits!"
}

Error responses

HTTPCodeWhen
403upgrade_requiredReward is gated behind a plan tier the org isn't on.
404not_foundUnknown reward key.
422action_not_completedThe qualifying action hasn't actually happened yet.
422already_claimedReward has already been claimed up to its max_claims limit.
429rate_limitedPer-second API rate limit.

Google Sheets Export

Connect your Google account to export batch results directly to Sheets.

GET /sheets/connect

Returns a Google OAuth URL. Redirect the user to this URL to initiate the connection flow.

GET /sheets/status

Check whether your org has a connected Google Sheets account.

{"connected": true, "email": "[email protected]"}

DELETE /sheets/disconnect

Disconnect Google Sheets. Requires admin role.

POST /batch/{id}/export/sheets

Export a completed batch to a new Google Sheet. Requires member role and an active Sheets connection.

Rate Limits

Monthly inspection capacity and the per-second request throttle are separate controls. The legacy X-RateLimit-* response headers are compatibility aliases for monthly check capacity:

X-RateLimit-Limit: 15000
X-RateLimit-Remaining: 10782
X-RateLimit-Reset: 1711929600
HeaderDescription
X-RateLimit-LimitCompatibility alias for your monthly check limit
X-RateLimit-RemainingCompatibility alias for checks remaining this month
X-RateLimit-ResetCompatibility alias for the monthly reset timestamp

Call GET /usage to discover the effective api_requests_per_sec throttle and other runtime limits. The throttle is shared by the organization across its API keys and sessions and applies only to protected execution routes. A 429 response includes Retry-After; the compatibility headers do not report per-second remaining capacity.

Monthly check limits

PlanChecks/monthBatch sizeAPI requests/sec
Free25025 URLs10
Pro ($8/mo)15,000200 URLs25
Business ($25/mo)100,000500 URLs50

These are the standard plan defaults. Where an organization has an administrative override, GET /usage returns the value that the API actually enforces.

When your monthly limit is exhausted, requests return 429 Too Many Requests. If you have check credits, they are consumed automatically and the request succeeds.

API Errors

All errors return a consistent JSON envelope with a request_id for debugging:

{
  "error": {
    "code": "rate_limited",
    "message": "Monthly check limit exceeded. Purchase credits or upgrade your plan.",
    "request_id": "7c3a1f2e-4b5d-4e6f-8a9b-0c1d2e3f4a5b"
  }
}

Rate limit errors (429) include a Retry-After header indicating how many seconds to wait before retrying.

CodeHTTP StatusDescription
unauthorized401Missing, invalid, or expired auth token / API key
forbidden403Insufficient role permissions
upgrade_required403Feature requires a paid plan (presets, team management, body retrieval)
not_found404Resource not found (or not accessible by your org)
validation_error422Invalid request body or parameters
rate_limited429Monthly check limit exceeded, per-second API rate limit exceeded, or maximum concurrent batch jobs reached
billing_disabled503Billing is not configured on this instance
sheets_disabled503Google Sheets is not configured on this instance
internal_error500Unexpected server error

Inspection Errors

These error codes appear inside the error field of an inspect response. They describe what went wrong when checking a specific URL. Distinct from API errors which are HTTP-level failures.

CodeDescription
dns_failureDNS lookup failed. Hostname doesn't resolve
connection_refusedTCP connection was refused by the remote server
connection_timeoutTCP connection timed out (couldn't reach the server)
tls_errorTLS handshake failed (generic)
tls_cert_expiredServer's TLS certificate has expired
tls_cert_invalidServer's TLS certificate is invalid (wrong hostname, untrusted CA, etc.)
response_timeoutServer accepted connection but didn't respond in time
body_timeoutResponse body download timed out
too_many_redirectsRedirect chain exceeded the max_redirects limit (chain of distinct URLs)
redirect_loopRedirect chain revisited a URL it had already seen. The site is actually looping, not just verbose
ssrf_blockedURL resolved to a private/internal IP address (SSRF protection)
url_blockedHistorical result from the removed Google Safe Browsing integration. New inspections do not produce this code.
invalid_redirectRedirect Location header was missing or malformed
response_too_largeResponse body exceeded 256 KB limit
protocol_errorHTTP protocol violation or malformed response
unknownUnclassified error. See the message field for details
Inspection errors do not affect the HTTP status of the API response. A POST /inspect that encounters a DNS failure still returns HTTP 200. The error is in the response body's error field. The API returns non-200 status codes only for API-level errors (auth, validation, rate limits).