API Documentation
Full reference for the 4F REST API. Base URL: https://www.4f.at/api/v1
Quick Start
The 4F API lets you manage projects, trigger scans, and retrieve results without touching the dashboard. Everything is available on the Free plan and above.
1. Get an API key
Go to Account → API keys and create a new key. You can scope it to all projects or restrict it to one or more specific projects. The key is only shown once – save it somewhere safe.
2. List your projects
curl https://www.4f.at/api/v1/projects \ -H "Authorization: Bearer 4f_your_key_here"
3. Trigger a scan
curl -X POST https://www.4f.at/api/v1/projects/PROJECT_ID/scans \
-H "Authorization: Bearer 4f_your_key_here"
# → { "scanId": "uuid" }4. Poll until complete
curl https://www.4f.at/api/v1/scans/SCAN_ID \
-H "Authorization: Bearer 4f_your_key_here"
# → { "status": "completed", "totalIssuesFound": 12, ... }5. Retrieve broken links
curl "https://www.4f.at/api/v1/scans/SCAN_ID/results?issues_only=true" \ -H "Authorization: Bearer 4f_your_key_here"
Authentication
All API requests require a Bearer token in the Authorization header. Keys start with 4f_.
Authorization: Bearer 4f_your_key_here
Global scope – can access and manage all projects on your account.
Project scope – restricted to one or more specific projects; requests for any other project return 403 Forbidden. Use this when giving a script access to one site without exposing your full account.
Account
Check your current plan, page quota, and billing period before triggering large scans.
/api/v1/accountReturns your current plan, page quota usage, and when the billing period started. Requires a global API key – project-scoped keys are rejected to prevent exposing account-level billing info to third parties.
Response
{
"plan": "starter",
"quotaUsed": 4821,
"quotaMonthly": 15000,
"bonusPages": 0,
"quotaRemaining": 10179,
"billingPeriodStart": "2026-06-01T00:00:00.000Z"
}Example
curl https://www.4f.at/api/v1/account \ -H "Authorization: Bearer 4f_your_key_here"
Projects
Projects are the core unit in 4F. Each project has a root URL – the crawler starts there and follows all internal links. Personal and team projects appear in the same list.
List projects
/api/v1/projectsReturns all projects you have access to, including team projects where you are a member. Each project includes a type field: "personal" or "team". Filter with ?type=personal or ?type=team.
Parameters
typestring"personal" | "team" – filter by project type. Omit to return all.typestring"personal" | "team" – filter by project type. Omit to return all.
Response
[
{
"id": "uuid",
"name": "My site",
"rootUrl": "https://example.com",
"scanFrequency": "weekly",
"orgId": null,
"type": "personal",
"createdAt": "2026-01-15T10:00:00.000Z"
},
{
"id": "uuid",
"name": "Client A",
"rootUrl": "https://client-a.com",
"scanFrequency": "manual",
"orgId": "org-uuid",
"type": "team",
"createdAt": "2026-02-10T09:00:00.000Z"
}
]Example
# All projects curl https://www.4f.at/api/v1/projects \ -H "Authorization: Bearer 4f_your_key_here" # Personal only curl "https://www.4f.at/api/v1/projects?type=personal" \ -H "Authorization: Bearer 4f_your_key_here"
Create project
/api/v1/projectsCreates a new personal project. Triggers a server lookup (favicon, SSL, robots.txt) immediately. Project-scoped keys cannot create projects.
Parameters
namestringrequiredDisplay name for the project.rootUrlstringrequiredThe root URL to crawl, e.g. https://example.com. Must include the protocol.isPersonalbooleanDefault: true (personal, hidden from team members). Pass false to create a team project visible to org members.scanFrequencystring"manual" | "weekly" | "daily". Default: "manual". weekly requires Starter+, daily requires Agency.crawlConfig.maxDepthnumberMaximum link depth to follow (1–20).crawlConfig.ignorePatternsstring[]URL substrings to skip.crawlConfig.respectRobotsbooleanWhether to honour robots.txt. Default: true.crawlConfig.emailAlertsstring"always" | "new_issues" | "disabled".crawlConfig.sitemapUrlstringCustom sitemap URL (Starter+ only). Must be on the same domain.crawlConfig.slowThresholdMsnumberResponse time above which a URL is flagged as slow, in ms. Default: 3000.crawlConfig.rateLimitnumberRequests per second: 1, 2, 3 (Free max), 5 (Starter max), 10 (Agency max). Default: 2.crawlConfig.scheduledHournumberHour of day to run the scan (0–23, UTC or scheduledTimezone). Required when scanFrequency is weekly or daily.crawlConfig.scheduledWeekdaynumberDay of week to run (0=Sun … 6=Sat). Required when scanFrequency is weekly.crawlConfig.scheduledTimezonestringAny valid IANA timezone identifier, e.g. "Europe/Vienna" or "America/New_York". Defaults to "UTC" if omitted or unrecognised.namestringrequiredDisplay name for the project.
rootUrlstringrequiredThe root URL to crawl, e.g. https://example.com. Must include the protocol.
isPersonalbooleanDefault: true (personal, hidden from team members). Pass false to create a team project visible to org members.
scanFrequencystring"manual" | "weekly" | "daily". Default: "manual". weekly requires Starter+, daily requires Agency.
crawlConfig.maxDepthnumberMaximum link depth to follow (1–20).
crawlConfig.ignorePatternsstring[]URL substrings to skip.
crawlConfig.respectRobotsbooleanWhether to honour robots.txt. Default: true.
crawlConfig.emailAlertsstring"always" | "new_issues" | "disabled".
crawlConfig.sitemapUrlstringCustom sitemap URL (Starter+ only). Must be on the same domain.
crawlConfig.slowThresholdMsnumberResponse time above which a URL is flagged as slow, in ms. Default: 3000.
crawlConfig.rateLimitnumberRequests per second: 1, 2, 3 (Free max), 5 (Starter max), 10 (Agency max). Default: 2.
crawlConfig.scheduledHournumberHour of day to run the scan (0–23, UTC or scheduledTimezone). Required when scanFrequency is weekly or daily.
crawlConfig.scheduledWeekdaynumberDay of week to run (0=Sun … 6=Sat). Required when scanFrequency is weekly.
crawlConfig.scheduledTimezonestringAny valid IANA timezone identifier, e.g. "Europe/Vienna" or "America/New_York". Defaults to "UTC" if omitted or unrecognised.
Response
{
"id": "uuid",
"name": "My site",
"rootUrl": "https://example.com",
"scanFrequency": "weekly",
"crawlConfig": {
"scheduledHour": 3,
"scheduledWeekday": 1,
"scheduledTimezone": "Europe/Vienna"
},
"createdAt": "2026-06-23T12:00:00.000Z"
}Example
# Personal project (default)
curl -X POST https://www.4f.at/api/v1/projects \
-H "Authorization: Bearer 4f_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name": "My site", "rootUrl": "https://example.com"}'
# Team project with weekly schedule
curl -X POST https://www.4f.at/api/v1/projects \
-H "Authorization: Bearer 4f_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Client site",
"rootUrl": "https://example.com",
"isPersonal": false,
"scanFrequency": "weekly",
"crawlConfig": {
"scheduledHour": 3,
"scheduledWeekday": 1,
"scheduledTimezone": "Europe/Vienna"
}
}'Get project
/api/v1/projects/:idReturns a single project including its crawl configuration (depth, ignore patterns, schedule, etc.).
Response
{
"id": "uuid",
"name": "My site",
"rootUrl": "https://example.com",
"scanFrequency": "weekly",
"crawlConfig": {
"maxDepth": 5,
"ignorePatterns": ["/wp-admin"],
"respectRobots": true,
"emailAlerts": "new_issues"
},
"orgId": null,
"createdAt": "2026-01-15T10:00:00.000Z"
}Example
curl https://www.4f.at/api/v1/projects/PROJECT_ID \ -H "Authorization: Bearer 4f_your_key_here"
Update project
/api/v1/projects/:idUpdates a project's name, scan schedule, or crawl settings. All fields are optional – only send what you want to change. The root URL cannot be changed via API; delete the project and create a new one instead. Changing scanFrequency is plan-gated: weekly requires Starter or higher, daily requires Agency.
Parameters
namestringNew display name.scanFrequencystring"manual" | "weekly" | "daily"crawlConfig.maxDepthnumberMaximum link depth to follow (1–20).crawlConfig.ignorePatternsstring[]URL substrings to skip.crawlConfig.respectRobotsbooleanWhether to honour robots.txt. Defaults to true.crawlConfig.emailAlertsstring"always" | "new_issues" | "disabled"crawlConfig.sitemapUrlstringCustom sitemap URL (Starter+ only). Must be on the same domain.crawlConfig.slowThresholdMsnumberResponse time above which a URL is flagged as slow, in ms. Default: 3000.crawlConfig.rateLimitnumberRequests per second: 1, 2, 3 (Free max), 5 (Starter max), 10 (Agency max). Default: 2.crawlConfig.scheduledHournumberHour of day to run the scan (0–23, UTC or scheduledTimezone).crawlConfig.scheduledWeekdaynumberDay of week to run (0=Sun … 6=Sat). Only used when scanFrequency is weekly.crawlConfig.scheduledTimezonestringIANA timezone for the schedule, e.g. "Europe/Vienna". Defaults to "UTC" if unrecognised.namestringNew display name.
scanFrequencystring"manual" | "weekly" | "daily"
crawlConfig.maxDepthnumberMaximum link depth to follow (1–20).
crawlConfig.ignorePatternsstring[]URL substrings to skip.
crawlConfig.respectRobotsbooleanWhether to honour robots.txt. Defaults to true.
crawlConfig.emailAlertsstring"always" | "new_issues" | "disabled"
crawlConfig.sitemapUrlstringCustom sitemap URL (Starter+ only). Must be on the same domain.
crawlConfig.slowThresholdMsnumberResponse time above which a URL is flagged as slow, in ms. Default: 3000.
crawlConfig.rateLimitnumberRequests per second: 1, 2, 3 (Free max), 5 (Starter max), 10 (Agency max). Default: 2.
crawlConfig.scheduledHournumberHour of day to run the scan (0–23, UTC or scheduledTimezone).
crawlConfig.scheduledWeekdaynumberDay of week to run (0=Sun … 6=Sat). Only used when scanFrequency is weekly.
crawlConfig.scheduledTimezonestringIANA timezone for the schedule, e.g. "Europe/Vienna". Defaults to "UTC" if unrecognised.
Response
{
"id": "uuid",
"name": "My site (updated)",
"rootUrl": "https://example.com",
"scanFrequency": "daily",
"crawlConfig": { "respectRobots": false },
"orgId": null,
"createdAt": "2026-01-15T10:00:00.000Z"
}Example
curl -X PATCH https://www.4f.at/api/v1/projects/PROJECT_ID \
-H "Authorization: Bearer 4f_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "My site (updated)",
"scanFrequency": "daily",
"crawlConfig": { "respectRobots": false }
}'Delete project
/api/v1/projects/:idPermanently deletes the project and all its scan history. Only the project owner can delete. Returns 204 No Content on success. This cannot be undone.
Example
curl -X DELETE https://www.4f.at/api/v1/projects/PROJECT_ID \ -H "Authorization: Bearer 4f_your_key_here"
Team projects
If you are part of a team (Agency plan), use this endpoint to list only team projects. This mirrors the Team Projects view in the dashboard. Personal projects are not included.
/api/v1/org/projectsReturns all projects belonging to teams you are a member of, including the team name alongside each project.
Response
[
{
"id": "uuid",
"name": "Client A",
"rootUrl": "https://client-a.com",
"scanFrequency": "weekly",
"orgId": "org-uuid",
"orgName": "Acme Agency",
"createdAt": "2026-02-10T09:00:00.000Z"
}
]Example
curl https://www.4f.at/api/v1/org/projects \ -H "Authorization: Bearer 4f_your_key_here"
Scans
A scan is a single crawl run of a project. Triggering a scan queues a job for the next available crawler. Scans consume pages from your monthly quota. Poll GET /api/v1/scans/:id until status is completed or failed.
Trigger a scan
/api/v1/projects/:id/scansQueues a new scan for the given project. Returns immediately with a scanId – the actual crawl runs asynchronously. Pass { notify: true } to receive an email report when the scan completes.
Parameters
notifybooleanIf true, sends an email report to the project owner on completion.notifybooleanIf true, sends an email report to the project owner on completion.
Response
{ "scanId": "uuid" }Example
curl -X POST https://www.4f.at/api/v1/projects/PROJECT_ID/scans \
-H "Authorization: Bearer 4f_your_key_here"
# With email report on completion:
curl -X POST https://www.4f.at/api/v1/projects/PROJECT_ID/scans \
-H "Authorization: Bearer 4f_your_key_here" \
-H "Content-Type: application/json" \
-d '{"notify": true}'List scans
/api/v1/projects/:id/scansReturns a paginated list of scans for the given project, ordered newest first.
Parameters
pagenumberPage number, 1-based. Default: 1.limitnumberResults per page, max 100. Default: 20.pagenumberPage number, 1-based. Default: 1.
limitnumberResults per page, max 100. Default: 20.
Response
{
"data": [
{
"id": "uuid",
"status": "completed",
"triggeredBy": "api",
"totalUrlsCrawled": 312,
"totalPagesCrawled": 148,
"totalIssuesFound": 7,
"createdAt": "2026-06-23T10:00:00.000Z",
"startedAt": "2026-06-23T10:00:05.000Z",
"finishedAt": "2026-06-23T10:04:22.000Z"
}
],
"page": 1,
"limit": 20
}Example
curl "https://www.4f.at/api/v1/projects/PROJECT_ID/scans?page=1&limit=5" \ -H "Authorization: Bearer 4f_your_key_here"
Get scan
/api/v1/scans/:idReturns the status and summary of a single scan. Poll this endpoint every 5–10 seconds until status is completed or failed.
Response
{
"id": "uuid",
"status": "completed",
"triggeredBy": "api",
"rootUrl": "https://example.com",
"totalUrlsCrawled": 312,
"totalPagesCrawled": 148,
"totalIssuesFound": 7,
"createdAt": "2026-06-23T10:00:00.000Z",
"startedAt": "2026-06-23T10:00:05.000Z",
"finishedAt": "2026-06-23T10:04:22.000Z",
"errorMessage": null,
"projectId": "uuid"
}Example
# Poll until status is "completed" or "failed" until [ "$(curl -s https://www.4f.at/api/v1/scans/SCAN_ID \ -H "Authorization: Bearer 4f_your_key_here" | jq -r '.status')" = "completed" ]; do sleep 10 done
Delete scan
/api/v1/scans/:idPermanently deletes the scan and all its URL records. Only the project owner can delete. Returns 204 No Content. This cannot be undone.
Example
curl -X DELETE https://www.4f.at/api/v1/scans/SCAN_ID \ -H "Authorization: Bearer 4f_your_key_here"
Results
Scan results are the individual URLs checked during a crawl, each with its HTTP status, response time, and any issues found. Results are only available once a scan reaches completed status.
Get results
/api/v1/scans/:id/resultsReturns paginated URL records for a scan. Each record includes all issues found on that URL. Use issues_only=true to skip URLs with no problems. Use limit=0 to return everything at once (suitable for smaller scans).
Parameters
pagenumber1-based page number. Default: 1.limitnumberResults per page, max 200. Default: 50. Use 0 for all results at once.issues_onlybooleanDefault: true. Pass false to return all checked URLs including those with no issues.pagenumber1-based page number. Default: 1.
limitnumberResults per page, max 200. Default: 50. Use 0 for all results at once.
issues_onlybooleanDefault: true. Pass false to return all checked URLs including those with no issues.
Response
{
"data": [
{
"url": "https://example.com/about",
"urlType": "page",
"statusCode": 404,
"responseTimeMs": 210,
"finalUrl": null,
"redirectCount": 0,
"firstStatusCode": null,
"foundOnUrl": "https://example.com/",
"linkText": "About us",
"errorType": null,
"contentType": "text/html",
"issues": [
{ "issueType": "not_found", "severity": "error", "detail": null }
]
}
],
"page": 1,
"limit": 50
}Example
# Broken URLs only (default) curl "https://www.4f.at/api/v1/scans/SCAN_ID/results" \ -H "Authorization: Bearer 4f_your_key_here" # All checked URLs including clean ones curl "https://www.4f.at/api/v1/scans/SCAN_ID/results?issues_only=false" \ -H "Authorization: Bearer 4f_your_key_here" # All broken URLs at once, no pagination curl "https://www.4f.at/api/v1/scans/SCAN_ID/results?limit=0" \ -H "Authorization: Bearer 4f_your_key_here"
Export CSV
/api/v1/scans/:id/exportDownloads the scan results as a CSV file. Columns: URL, Type, Status Code, Response Time (ms), Issue Types, Severity, Found On, Link Text. The response includes Content-Disposition: attachment so browsers trigger a file download.
Parameters
broken1 | 0Set to 1 to include only URLs with issues. Default: 0 (all URLs).broken1 | 0Set to 1 to include only URLs with issues. Default: 0 (all URLs).
Example
# Download broken links as CSV curl "https://www.4f.at/api/v1/scans/SCAN_ID/export?broken=1" \ -H "Authorization: Bearer 4f_your_key_here" \ -o broken-links.csv # All results curl "https://www.4f.at/api/v1/scans/SCAN_ID/export" \ -H "Authorization: Bearer 4f_your_key_here" \ -o scan-results.csv
Webhooks
Webhooks are HTTP callbacks delivered to your server when a scan event occurs. Instead of polling the API, your endpoint receives a POST request the moment a scan completes or fails. Webhooks are available on all plans and configured per project in the project settings.
Events
scan.completedFires when a scan finishes successfully with results.scan.failedFires when the crawler encounters an unrecoverable error.Payload
4F always sends Content-Type: application/json. The body shape depends on the event:
scan.completed
{
"event": "scan.completed",
"scanId": "uuid",
"projectId": "uuid",
"projectUrl": "https://example.com",
"totalPages": 142,
"totalIssues": 7,
"finishedAt": "2026-06-23T14:22:00.000Z"
}scan.failed
{
"event": "scan.failed",
"scanId": "uuid",
"projectId": "uuid",
"projectUrl": "https://example.com",
"error": "connection refused"
}Signature verification
Every delivery includes an X-4F-Signature: sha256=… header. Compute HMAC-SHA256(secret, rawBody) on your end and compare. The secret is shown once when you create the webhook.
Node.js
import crypto from "crypto"
const sig = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(rawBody) // raw Buffer before JSON.parse
.digest("hex")
if (`sha256=${sig}` !== req.headers["x-4f-signature"]) {
return res.status(401).end()
}PHP
$sig = 'sha256=' . hash_hmac('sha256', $rawBody, $_ENV['WEBHOOK_SECRET']);
if (!hash_equals($sig, $_SERVER['HTTP_X_4F_SIGNATURE'])) {
http_response_code(401);
exit;
}Authorization header
In addition to the signature, you can configure an Authorization header value per webhook. 4F includes it unchanged on every delivery –useful for tools like n8n or Zapier that authenticate via a token rather than HMAC.
Bearer <token>Paste a token from your receiving service.Basic <base64>Base64-encoded user:password for HTTP Basic auth.CustomAny raw header value, e.g. Token abc123 or ApiKey xyz.The Authorization header is stored encrypted. Always verify the HMAC signature as well – the header alone does not prove the payload was not modified in transit.
Errors
All errors return JSON with an error string describing the problem.
401UnauthorizedMissing or invalid API key, or key has been revoked.403ForbiddenYour key does not have access to this project.404Not foundProject or scan does not exist.422UnprocessableValidation error – check the error message for details.429Rate limitedYou've exceeded 50 requests/minute, or your monthly page quota is exhausted.{ "error": "Project not found" }Rate limits
API request rate: 50 requests per minute per API key. Exceeding this returns 429 Too Many Requests.
Page quota: Each triggered scan draws from your monthly page quota. If you exhaust your quota, POST /api/v1/projects/:id/scans returns 429 with "Monthly page quota exhausted". Check GET /api/v1/account to see your current usage.
Domain scan rate: The same domain cannot be scanned more than once every few minutes. This applies per domain, not per project.