openapi: 3.0.3
info:
  title: Dead Link Checker
  description: |
    Crawl any public website and find broken links AND broken images.
    NEW in v2.9: max_duration timeout — returns partial results instead of 500 errors on large sites.
    v2.8: RESULT: PASS/FAIL line in github and markdown formats when threshold is set — single API call for CI/CD gating.
    v2.7: GitHub README link checking (github=owner/repo) — check all links in any public repo's README.
    v2.6: Markdown output (format=markdown) — formatted reports for PR comments and Slack.
    v2.5: GitHub Actions annotation output (format=github) — pipe results directly
    into GitHub Actions workflow commands for native build annotations.
    v2.4: CI/CD pipeline features — threshold parameter for pass/fail gating
    (fail builds when broken links exceed threshold), and check_only parameter to filter
    by internal or external links only. Combined with quick mode for sub-second checks.
    v2.3: Quick mode — check all links on a single page without a browser in
    under a second. Perfect for CI/CD pipelines and monitoring. Use mode=quick.
    v2.2 features:
    response time measurement per link (ms), slow link detection (>2s threshold),
    concurrent external link checking (10x faster), redirect chain detection showing every
    hop (301/302), HEAD-first with GET fallback for accuracy, mixed content detection
    (HTTP resources on HTTPS pages), and CSV export. Checks both internal pages and
    external links/images, reporting HTTP status codes, error details, link types
    (internal/external), resource types (anchor/image), response times, and anchor/alt
    text context. Includes summary statistics with health score, broken image count,
    redirect warnings, slow link count, and average/max response times. Useful for SEO
    audits, CI/CD integration, performance monitoring, and pre-deployment checks.

    **Authentication (optional):** Add an `X-API-Key` header for higher limits (5 req/min, 50/day vs anonymous 10/day cap).
    Get a free API key instantly at https://hermesforge.dev/tools/deadlinks or POST to `/api/keys` with `{}` (anonymous, 20/day) or `{"email": "you@example.com"}` (50/day).

    Without a key: 5 requests per minute per IP. With a free key: 5 req/min, 50/day, 500/month.
  version: "2.9.0"
  contact:
    name: Hermes Agent
    email: hermes-agent@agentmail.to
    url: https://hermesforge.dev/

externalDocs:
  description: Try this tool in the browser
  url: https://hermesforge.dev/tools/deadlinks

servers:
  - url: https://hermesforge.dev

paths:
  /api/deadlinks:
    get:
      operationId: checkDeadLinks
      summary: Check for broken links on a website or GitHub README
      description: |
        Crawls the specified URL and its internal pages (up to max_pages), checking all links
        for broken responses (4xx, 5xx, connection failures). Alternatively, use the github
        parameter to check all links in a GitHub repository's README.md file. Rate limited
        to 1 request per 5 minutes per IP. Large sites may take 30-60 seconds.
      parameters:
        - name: url
          in: query
          required: false
          description: The URL to start crawling from. Must be a public URL. Required unless github is specified.
          schema:
            type: string
            example: "https://example.com"
        - name: github
          in: query
          required: false
          description: |
            GitHub repository in owner/repo format. Fetches the README.md and checks all links.
            Supports main, master branches and README.md/readme.md variants.
            Use instead of url parameter.
          schema:
            type: string
            example: "astral-sh/ruff"
        - name: mode
          in: query
          required: false
          description: |
            Check mode. 'quick' checks links on a single page without launching a browser
            (sub-second response, ideal for CI/CD). 'full' crawls multiple pages with a
            headless browser (thorough but slower, 30-60s for large sites).
          schema:
            type: string
            enum: [quick, full]
            default: full
        - name: max_pages
          in: query
          required: false
          description: Maximum number of internal pages to crawl.
          schema:
            type: integer
            default: 20
            maximum: 50
            example: 20
        - name: check_only
          in: query
          required: false
          description: |
            Filter links by type. 'internal' checks only links pointing to the same domain.
            'external' checks only links pointing to other domains. Omit to check all links.
            Only applies to mode=quick.
          schema:
            type: string
            enum: [internal, external]
        - name: threshold
          in: query
          required: false
          description: |
            Maximum number of broken links before the result includes "pass": false.
            Designed for CI/CD pipelines: fail the build when broken links exceed this number.
            When set, the response includes "pass" (boolean) and "threshold" (integer) fields.
            Only applies to mode=quick.
          schema:
            type: integer
            minimum: 0
            example: 0
        - name: max_duration
          in: query
          required: false
          description: |
            Maximum crawl duration in seconds. If the crawl exceeds this time, partial results
            are returned with a "timed_out": true flag instead of a 500 error. Useful for large
            sites where you want best-effort results within a time budget. Default: no limit.
          schema:
            type: integer
            minimum: 10
            maximum: 120
            example: 60
        - name: format
          in: query
          required: false
          description: |
            Output format. 'json' for structured data, 'csv' for spreadsheet-friendly export,
            'github' for GitHub Actions workflow annotations (::error::, ::warning::, ::notice::),
            'markdown' for formatted Markdown reports (ideal for PR comments and Slack).
            Use format=github in CI/CD pipelines for native build annotations.
          schema:
            type: string
            enum: [json, csv, github, markdown]
            default: json
      responses:
        "200":
          description: Crawl completed successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeadLinkReport"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: Crawl failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

security:
  - ApiKeyAuth: []
  - {}

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Optional API key for higher rate limits. Get one free at
        https://hermesforge.dev/tools/deadlinks or POST /api/keys with {} (anonymous, 20/day) or {"email": "you@example.com"} (50/day).
  schemas:
    DeadLinkReport:
      type: object
      properties:
        target:
          type: string
          description: The starting URL that was crawled.
          example: "https://example.com"
        pages_crawled:
          type: integer
          description: Number of internal pages visited.
          example: 15
        total_links_checked:
          type: integer
          description: Total number of links checked (internal + external).
          example: 142
        working_links:
          type: integer
          description: Number of working (non-broken) links found.
          example: 139
        broken_links:
          type: array
          items:
            $ref: "#/components/schemas/BrokenLink"
        broken_count:
          type: integer
          description: Number of broken links found.
          example: 3
        summary:
          type: object
          description: Link statistics by type.
          properties:
            internal_links:
              type: integer
              description: Total internal links discovered.
              example: 85
            external_links:
              type: integer
              description: Total external links discovered.
              example: 57
            broken_internal:
              type: integer
              description: Number of broken internal links.
              example: 1
            broken_external:
              type: integer
              description: Number of broken external links.
              example: 2
            broken_images:
              type: integer
              description: Number of broken images found.
              example: 0
            mixed_content:
              type: integer
              description: Number of HTTP resources found on HTTPS pages (security/SEO issue).
              example: 0
            health_score:
              type: number
              format: float
              description: Percentage of links that are working (0-100).
              example: 97.9
            redirects_found:
              type: integer
              description: Number of working links that involve redirects.
              example: 3
        redirect_warnings:
          type: array
          description: Working links that redirect (potential SEO issues). Included when redirects are detected.
          items:
            $ref: "#/components/schemas/RedirectWarning"
        mixed_content:
          type: array
          description: HTTP resources found on HTTPS pages. Included when mixed content is detected.
          items:
            $ref: "#/components/schemas/MixedContent"
        timestamp:
          type: string
          format: date-time
          example: "2026-02-26T15:30:00Z"
    BrokenLink:
      type: object
      properties:
        url:
          type: string
          description: The broken URL.
          example: "https://example.com/broken-page"
        source:
          type: string
          description: The page where this broken link was found.
          example: "https://example.com/links"
        status:
          type: integer
          description: HTTP status code (0 for connection failures).
          example: 404
        error:
          type: string
          description: Human-readable error description.
          example: "Not Found"
        link_type:
          type: string
          enum: [internal, external]
          description: Whether the broken link is internal or external to the target domain.
          example: "external"
        resource_type:
          type: string
          enum: [anchor, image]
          description: Whether this is a broken link (anchor) or broken image.
          example: "anchor"
        link_text:
          type: string
          description: The anchor text or image alt text (max 100 chars).
          example: "Click here"
        redirect_chain:
          type: array
          description: Redirect hops before reaching the final (broken) URL. Included when redirects occurred.
          items:
            type: object
            properties:
              url:
                type: string
              status:
                type: integer
    RedirectWarning:
      type: object
      properties:
        url:
          type: string
          description: The original URL that redirects.
          example: "http://example.com/old-page"
        source:
          type: string
          description: The page where this link was found.
          example: "https://example.com/"
        final_url:
          type: string
          description: The URL after all redirects.
          example: "https://example.com/new-page"
        hops:
          type: integer
          description: Number of redirect hops.
          example: 2
        redirect_chain:
          type: array
          description: Each redirect hop with URL and status code.
          items:
            type: object
            properties:
              url:
                type: string
              status:
                type: integer
        link_text:
          type: string
          description: The anchor text of the link.
    MixedContent:
      type: object
      properties:
        url:
          type: string
          description: The HTTP resource URL.
          example: "http://example.com/image.jpg"
        source:
          type: string
          description: The HTTPS page where this HTTP resource was found.
          example: "https://example.com/"
        resource_type:
          type: string
          enum: [anchor, image]
          description: Type of resource (link or image).
        issue:
          type: string
          description: Description of the issue.
          example: "HTTP resource loaded on HTTPS page"
    Error:
      type: object
      properties:
        error:
          type: string
          example: "Rate limit exceeded. Max 1 crawl per 5 minutes."
