Skip to content

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.

GET/api/v1/account

Returns 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

GET/api/v1/projects

Returns 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.

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

POST/api/v1/projects

Creates a new personal project. Triggers a server lookup (favicon, SSL, robots.txt) immediately. Project-scoped keys cannot create projects.

Parameters

namestringrequired

Display name for the project.

rootUrlstringrequired

The root URL to crawl, e.g. https://example.com. Must include the protocol.

isPersonalboolean

Default: 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.maxDepthnumber

Maximum link depth to follow (1–20).

crawlConfig.ignorePatternsstring[]

URL substrings to skip.

crawlConfig.respectRobotsboolean

Whether to honour robots.txt. Default: true.

crawlConfig.emailAlertsstring

"always" | "new_issues" | "disabled".

crawlConfig.sitemapUrlstring

Custom sitemap URL (Starter+ only). Must be on the same domain.

crawlConfig.slowThresholdMsnumber

Response time above which a URL is flagged as slow, in ms. Default: 3000.

crawlConfig.rateLimitnumber

Requests per second: 1, 2, 3 (Free max), 5 (Starter max), 10 (Agency max). Default: 2.

crawlConfig.scheduledHournumber

Hour of day to run the scan (0–23, UTC or scheduledTimezone). Required when scanFrequency is weekly or daily.

crawlConfig.scheduledWeekdaynumber

Day of week to run (0=Sun … 6=Sat). Required when scanFrequency is weekly.

crawlConfig.scheduledTimezonestring

Any 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

GET/api/v1/projects/:id

Returns 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

PATCH/api/v1/projects/:id

Updates 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

namestring

New display name.

scanFrequencystring

"manual" | "weekly" | "daily"

crawlConfig.maxDepthnumber

Maximum link depth to follow (1–20).

crawlConfig.ignorePatternsstring[]

URL substrings to skip.

crawlConfig.respectRobotsboolean

Whether to honour robots.txt. Defaults to true.

crawlConfig.emailAlertsstring

"always" | "new_issues" | "disabled"

crawlConfig.sitemapUrlstring

Custom sitemap URL (Starter+ only). Must be on the same domain.

crawlConfig.slowThresholdMsnumber

Response time above which a URL is flagged as slow, in ms. Default: 3000.

crawlConfig.rateLimitnumber

Requests per second: 1, 2, 3 (Free max), 5 (Starter max), 10 (Agency max). Default: 2.

crawlConfig.scheduledHournumber

Hour of day to run the scan (0–23, UTC or scheduledTimezone).

crawlConfig.scheduledWeekdaynumber

Day of week to run (0=Sun … 6=Sat). Only used when scanFrequency is weekly.

crawlConfig.scheduledTimezonestring

IANA 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

DELETE/api/v1/projects/:id

Permanently 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.

GET/api/v1/org/projects

Returns 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

POST/api/v1/projects/:id/scans

Queues 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

notifyboolean

If 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

GET/api/v1/projects/:id/scans

Returns a paginated list of scans for the given project, ordered newest first.

Parameters

pagenumber

Page number, 1-based. Default: 1.

limitnumber

Results 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

GET/api/v1/scans/:id

Returns 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

DELETE/api/v1/scans/:id

Permanently 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

GET/api/v1/scans/:id/results

Returns 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

pagenumber

1-based page number. Default: 1.

limitnumber

Results per page, max 200. Default: 50. Use 0 for all results at once.

issues_onlyboolean

Default: 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

GET/api/v1/scans/:id/export

Downloads 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 | 0

Set 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.

Questions? hello@4f.at · FAQ