{
  "openapi": "3.1.0",
  "info": {
    "title": "Bare Audit API",
    "version": "1.0.0",
    "summary": "Server-to-server API for bounded mobile page audits and saved reports.",
    "description": "Bare Audit API v1. Use server-to-server over HTTPS. API keys are issued without an account and are revocable; the secret is returned only once. This contract does not include rankings publication or site crawling. Usage is quota-limited; no unlimited-use or hosting availability guarantee is made. Responses are no-store and marked noindex. Browser cross-origin CORS is not supported. Never put API keys in URLs or logs. The Bare developer webpage keeps a newly issued key in page memory only; server-to-server API clients should store it in a server-side secret manager or environment, never browser localStorage. Do not include keys in submitted page URLs."
  },
  "tags": [
    {
      "name": "Audits",
      "description": "Bounded mobile audit creation or reuse."
    },
    {
      "name": "Reports",
      "description": "Read-only access to an existing saved report."
    },
    {
      "name": "Keys",
      "description": "Create and manage 30-day bearer API keys."
    }
  ],
  "paths": {
    "/api/v1/audits": {
      "post": {
        "tags": [
          "Audits"
        ],
        "operationId": "createOrReuseAudit",
        "summary": "Start or reuse a mobile page audit",
        "description": "Creates one bounded mobile audit for a public page, or reuses an eligible pending/cached result. Normal reports, including partial reports, are reusable for 24 hours. refresh:true reuses a finished report younger than five minutes; active pending work is reused first. It does not bypass scan quotas. GET /api/v1/reports/{id} only reads a report and never starts a scan. Reports remain readable by anyone with the report ID until seven-day expiry, even though this API operation requires a key. Do not submit sensitive URLs or secrets in the URL. No ranking publication is performed. Every API request is limited to 60 requests per fixed UTC minute per source IP. Authenticated requests also have a 60-request fixed UTC-minute limit per key; this counts cached reads and calls that fail validation or processing after authentication. All v1 requests also share a service-wide limit of 10,000 API requests per UTC day, independent of the separate 500-new-scan daily cap. This request budget includes key creation and management, invalid authentication attempts, report reads, cache hits, and audit requests, and is consumed before the per-IP/per-key request counters. The website and API share the per-IP scan budget. Uncached scan reservations additionally require available per-key and per-IP budgets of 3 per rolling minute, 10 per rolling hour, and 20 per UTC day, plus the shared service budget of 500 new scans per UTC day, four active scans, and a 15-second exact-hostname cooldown. Cache hits do not consume a new-scan reservation. Once reserved, failed scans count. All 429 responses include Retry-After. Submitted URLs and report data have a seven-day access window from reservation. Physical deletion is lazy and may occur later. Raw client IPs are not stored; a salted daily hash is used for quota accounting.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON body limited to 4 KiB. URL is one public page, not a crawl. Optional dkimSelector checks only the declared selector.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAuditRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Illustrative request for one reserved example-domain page",
                  "value": {
                    "url": "https://example.com/"
                  }
                },
                "refresh": {
                  "summary": "Illustrative explicit refresh request using a sample selector",
                  "value": {
                    "url": "https://example.com/",
                    "refresh": true,
                    "dkimSelector": "mail"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A complete or partial report was returned from cache or after the scan completed.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompletedAuditResponse"
                },
                "examples": {
                  "reservedExample": {
                    "$ref": "#/components/examples/CompletedExample"
                  }
                }
              }
            }
          },
          "202": {
            "description": "A report reservation is pending. Poll its ID with GET /api/v1/reports/{id}; polling does not start or restart work.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingAuditResponse"
                },
                "examples": {
                  "pending": {
                    "value": {
                      "status": "pending",
                      "id": "00000000-0000-4000-8000-000000000001",
                      "measuredAt": null,
                      "expiresAt": "2026-10-23T12:00:00Z",
                      "returnedAt": "2026-10-16T12:00:00Z",
                      "cacheHit": false,
                      "profile": {
                        "device": "mobile",
                        "scope": "single-page"
                      },
                      "scoreInfo": null,
                      "retryAfter": 4
                    },
                    "summary": "Illustrative pending response with a reserved UUID and sample timestamps"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamFailure"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/reports/{id}": {
      "get": {
        "tags": [
          "Reports"
        ],
        "operationId": "getSavedReport",
        "summary": "Read a saved audit report",
        "description": "Reads an existing report and never starts, queues, or refreshes a scan. Polling does not extend report retention. Reports are readable to anyone with the report ID/link until their seven-day expiry, even though this API operation requires a key. The GET operation has the general per-IP/per-key request rate limits but does not consume a new-scan reservation. Every API request is limited to 60 requests per fixed UTC minute per source IP. Authenticated requests also have a 60-request fixed UTC-minute limit per key; this counts cached reads and calls that fail validation or processing after authentication. All v1 requests also share a service-wide limit of 10,000 API requests per UTC day, independent of the separate 500-new-scan daily cap. This request budget includes key creation and management, invalid authentication attempts, report reads, cache hits, and audit requests, and is consumed before the per-IP/per-key request counters. The website and API share the per-IP scan budget. Uncached scan reservations additionally require available per-key and per-IP budgets of 3 per rolling minute, 10 per rolling hour, and 20 per UTC day, plus the shared service budget of 500 new scans per UTC day, four active scans, and a 15-second exact-hostname cooldown. Cache hits do not consume a new-scan reservation. Once reserved, failed scans count. All 429 responses include Retry-After.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Illustrative reserved UUID returned by POST /api/v1/audits.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "00000000-0000-4000-8000-000000000001"
          }
        ],
        "responses": {
          "200": {
            "description": "The saved report is complete or partial. returnedAt is this response time; measuredAt remains the original scan time.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompletedAuditResponse"
                },
                "examples": {
                  "reservedExample": {
                    "$ref": "#/components/examples/CompletedExample"
                  }
                }
              }
            }
          },
          "202": {
            "description": "The existing report is pending. This response does not start or restart a scan.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingAuditResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/keys": {
      "post": {
        "tags": [
          "Keys"
        ],
        "operationId": "createApiKey",
        "summary": "Create an API key",
        "security": [],
        "description": "Issues one 30-day API key without an account or email address. The complete secret is returned once in this response and cannot be retrieved later. When created through the Bare developer webpage, keep it in page memory only and do not persist it in browser storage. Server-to-server clients should copy it immediately to a server-side secret manager or environment variable. Never put it in a URL or logs. Do not automatically retry creation or request automatic reissue: a timeout can leave a newly created key whose secret was not received. A lost key cannot be recovered; let it expire or revoke it if available, then create a replacement subject to issuance limits. Issuance is limited to one key creation per source IP per rolling minute, two per source IP per UTC day, and 100 creations per UTC day globally; revoked keys still count. All v1 requests share a 60-per-fixed-UTC-minute IP request limit and an independent 10,000-request UTC-day API budget, including key issuance. Published limits are ceilings, not a guarantee of availability or a pricing commitment.",
        "requestBody": {
          "required": true,
          "description": "Send an empty JSON object. No authentication is used for key creation.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKeyRequest"
              },
              "examples": {
                "emptyObject": {
                  "summary": "Illustrative empty JSON object required by key creation",
                  "value": {}
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. This is the only response that contains the full secret. The developer webpage keeps it in page memory only; server-to-server clients should store it in a server-side secret manager. It cannot be retrieved again.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedKeyResponse"
                },
                "examples": {
                  "illustrative": {
                    "$ref": "#/components/examples/CreatedKeyExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/key": {
      "get": {
        "tags": [
          "Keys"
        ],
        "operationId": "getCurrentApiKey",
        "summary": "Read current key metadata and limits",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "description": "Returns metadata and the per-key limit values for the presented key; shared per-IP and service-wide limits can apply sooner. The secret is never returned again. No email, account, or key recovery flow exists. This request is subject to the 60-per-fixed-UTC-minute IP limit and 60-per-fixed-UTC-minute active-key limit. All v1 calls, including this metadata read, count toward the shared 10,000-request UTC-day API budget.",
        "responses": {
          "200": {
            "description": "Key is active.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyInfoResponse"
                },
                "examples": {
                  "illustrative": {
                    "summary": "Illustrative key metadata and limits; not a real issued key",
                    "value": {
                      "keyInfo": {
                        "id": "00000000-0000-4000-8000-000000000002",
                        "prefix": "bare_live_01234567",
                        "createdAt": "2026-10-16T12:00:00Z",
                        "expiresAt": "2026-11-15T12:00:00Z"
                      },
                      "limits": {
                        "requestsPerMinute": 60,
                        "scansPerMinute": 3,
                        "scansPerHour": 10,
                        "scansPerDay": 20,
                        "serviceRequestsPerDay": 10000
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Keys"
        ],
        "operationId": "revokeCurrentApiKey",
        "summary": "Revoke the presented API key",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "description": "Revokes the key identified by the presented bearer secret. Possession of the key is the authority to manage it; there is no account recovery. A revoked key cannot be used again and still counts toward issuance limits. This request is subject to the 60-per-fixed-UTC-minute IP limit and 60-per-fixed-UTC-minute active-key limit. All v1 calls, including revocation, count toward the shared 10,000-request UTC-day API budget.",
        "responses": {
          "200": {
            "description": "Key revoked.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              },
              "X-Robots-Tag": {
                "$ref": "#/components/headers/NoIndex"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevokeKeyResponse"
                },
                "examples": {
                  "illustrative": {
                    "summary": "Illustrative successful revocation response",
                    "value": {
                      "revoked": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Bare API key",
        "description": "Bearer API key. Send the complete key only in the Authorization header over HTTPS. The service stores a cryptographic hash, keys expire after 30 days, and a key can revoke itself. Anyone who possesses an active key can use it within quotas."
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Seconds before retrying; present on pending and rate-limit responses.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "NoStore": {
        "description": "Responses containing audit status or report details are not stored by intermediary caches.",
        "schema": {
          "type": "string",
          "const": "no-store"
        }
      },
      "NoIndex": {
        "description": "API responses must not be indexed by crawlers.",
        "schema": {
          "type": "string",
          "const": "noindex, nofollow"
        }
      }
    },
    "schemas": {
      "CreateAuditRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Absolute public HTTP(S) page URL. Credentials, local/reserved targets, and unsafe redirects are rejected. One page only; no site crawl.",
            "pattern": "^https?://"
          },
          "refresh": {
            "type": "boolean",
            "default": false,
            "description": "When true, requests a fresh scan unless an eligible complete result younger than five minutes or active pending report can be reused. It does not bypass quotas."
          },
          "dkimSelector": {
            "type": "string",
            "maxLength": 63,
            "pattern": "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$",
            "description": "Optional exact DNS selector for the bounded DKIM declaration observation. It does not validate a signed message or discover arbitrary selectors."
          }
        }
      },
      "Profile": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "device",
          "scope"
        ],
        "properties": {
          "device": {
            "type": "string",
            "const": "mobile"
          },
          "scope": {
            "type": "string",
            "const": "single-page"
          }
        }
      },
      "PendingAuditResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "id",
          "measuredAt",
          "expiresAt",
          "returnedAt",
          "cacheHit",
          "profile",
          "scoreInfo",
          "retryAfter"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "pending"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "measuredAt": {
            "type": "null",
            "description": "No completed measurement exists yet."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Current seven-day report expiry from reservation; polling does not extend it."
          },
          "returnedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time this status response was assembled; distinct from measuredAt."
          },
          "cacheHit": {
            "type": "boolean",
            "description": "True when an existing pending/completed reservation was reused."
          },
          "profile": {
            "$ref": "#/components/schemas/Profile"
          },
          "scoreInfo": {
            "type": "null",
            "description": "Scores are unavailable until measurement completes."
          },
          "retryAfter": {
            "type": "integer",
            "minimum": 1,
            "description": "Suggested polling delay in seconds; also sent in the Retry-After header.",
            "const": 4
          }
        }
      },
      "CompletedAuditResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "id",
          "measuredAt",
          "expiresAt",
          "returnedAt",
          "cacheHit",
          "profile",
          "scoreInfo",
          "report"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "partial"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "measuredAt": {
            "type": "string",
            "format": "date-time",
            "description": "Original report generatedAt timestamp; unchanged on cache hits."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Original report expiry, currently seven days after reservation; reads do not extend retention."
          },
          "returnedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time this response was assembled; distinct from measuredAt."
          },
          "cacheHit": {
            "type": "boolean",
            "description": "True when a saved/pending result was reused; false for the request that performed the uncached scan."
          },
          "profile": {
            "$ref": "#/components/schemas/Profile"
          },
          "scoreInfo": {
            "$ref": "#/components/schemas/ScoreInfo"
          },
          "report": {
            "$ref": "#/components/schemas/AuditReport"
          }
        }
      },
      "ScoreInfo": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "bareScore",
          "categories"
        ],
        "properties": {
          "bareScore": {
            "$ref": "#/components/schemas/BareScore"
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryScore"
            }
          }
        }
      },
      "CategoryScore": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "score"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "performance",
              "weight",
              "seo",
              "accessibility",
              "security",
              "third-party",
              "field",
              "dns",
              "hosting"
            ]
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Null means unavailable; it is not a zero score."
          }
        }
      },
      "BareScore": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "status",
              "score",
              "grade",
              "components",
              "missing"
            ],
            "properties": {
              "method": {
                "type": "string",
                "const": "bare-score-v1"
              },
              "status": {
                "type": "string",
                "const": "available"
              },
              "score": {
                "type": "number",
                "minimum": 0,
                "maximum": 100
              },
              "grade": {
                "type": "string",
                "enum": [
                  "A",
                  "B",
                  "C",
                  "D",
                  "E",
                  "F"
                ]
              },
              "components": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BareScoreComponent"
                },
                "minItems": 3,
                "maxItems": 3
              },
              "missing": {
                "type": "array",
                "maxItems": 0,
                "items": {
                  "type": "string",
                  "enum": [
                    "performance",
                    "seo",
                    "accessibility"
                  ]
                }
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "status",
              "score",
              "grade",
              "components",
              "missing"
            ],
            "properties": {
              "method": {
                "type": "string",
                "const": "bare-score-v1"
              },
              "status": {
                "type": "string",
                "const": "unavailable"
              },
              "score": {
                "type": "null"
              },
              "grade": {
                "type": "null"
              },
              "components": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BareScoreComponent"
                },
                "minItems": 3,
                "maxItems": 3
              },
              "missing": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "performance",
                    "seo",
                    "accessibility"
                  ]
                },
                "minItems": 1,
                "maxItems": 3
              }
            }
          }
        ]
      },
      "BareScoreComponent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "label",
          "score"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "performance",
              "seo",
              "accessibility"
            ]
          },
          "label": {
            "type": "string"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          }
        }
      },
      "AuditReport": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "url",
          "finalUrl",
          "hostname",
          "generatedAt",
          "expiresAt",
          "device",
          "status",
          "categories",
          "findings",
          "resources",
          "thirdParties",
          "notices",
          "labAvailable",
          "fieldAvailable",
          "engine"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "pattern": "^https?://"
          },
          "finalUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "pattern": "^https?://"
          },
          "hostname": {
            "type": "string",
            "maxLength": 253
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "device": {
            "type": "string",
            "const": "mobile"
          },
          "status": {
            "type": "string",
            "enum": [
              "complete",
              "partial"
            ]
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            },
            "maxItems": 9
          },
          "findings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Finding"
            },
            "maxItems": 500
          },
          "resources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Resource"
            },
            "maxItems": 500
          },
          "thirdParties": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ThirdParty"
            },
            "maxItems": 100
          },
          "notices": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 1000
            },
            "maxItems": 50
          },
          "labAvailable": {
            "type": "boolean"
          },
          "fieldAvailable": {
            "type": "boolean"
          },
          "engine": {
            "type": "string",
            "maxLength": 100
          },
          "fieldScope": {
            "type": "string",
            "enum": [
              "url",
              "origin"
            ]
          },
          "bareScore": {
            "$ref": "#/components/schemas/BareScore"
          },
          "socialPreview": {
            "$ref": "#/components/schemas/SocialPreview"
          },
          "seoInsights": {
            "$ref": "#/components/schemas/SeoInsights"
          }
        }
      },
      "Category": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "label",
          "score",
          "metrics"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "performance",
              "weight",
              "seo",
              "accessibility",
              "security",
              "third-party",
              "field",
              "dns",
              "hosting"
            ]
          },
          "label": {
            "type": "string",
            "maxLength": 100
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Null when unavailable; never substitute zero for missing data."
          },
          "metrics": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Metric"
            },
            "maxItems": 200
          }
        }
      },
      "Metric": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "label",
          "value",
          "status",
          "description",
          "source"
        ],
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 100
          },
          "label": {
            "type": "string",
            "maxLength": 200
          },
          "value": {
            "type": "string",
            "maxLength": 2000
          },
          "unit": {
            "type": "string",
            "maxLength": 40
          },
          "status": {
            "type": "string",
            "enum": [
              "good",
              "warning",
              "poor",
              "info",
              "unavailable"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 3000
          },
          "source": {
            "type": "string",
            "enum": [
              "lab",
              "field",
              "html",
              "dns",
              "server",
              "estimate"
            ]
          },
          "numeric": {
            "type": "number"
          },
          "availability": {
            "type": "string",
            "enum": [
              "not-applicable",
              "could-not-measure",
              "not-published",
              "not-supported"
            ],
            "description": "Optional evidence for an unavailable metric. No score or pass is inferred. Historical reports may omit this reason; not-published means a matching registry response omitted the date."
          }
        }
      },
      "Finding": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "id",
          "title",
          "description",
          "impact",
          "category"
        ],
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 120
          },
          "title": {
            "type": "string",
            "maxLength": 300
          },
          "description": {
            "type": "string",
            "maxLength": 4000
          },
          "impact": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "performance",
              "weight",
              "seo",
              "accessibility",
              "security",
              "third-party",
              "field",
              "dns",
              "hosting"
            ]
          },
          "savings": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "Resource": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "type",
          "bytes",
          "requests"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 500
          },
          "type": {
            "type": "string",
            "maxLength": 80
          },
          "bytes": {
            "type": "integer",
            "minimum": 0
          },
          "requests": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ThirdParty": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "domain",
          "bytes",
          "requests"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "domain": {
            "type": "string",
            "maxLength": 253
          },
          "bytes": {
            "type": "integer",
            "minimum": 0
          },
          "requests": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "SocialPreview": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title",
          "description",
          "image",
          "url",
          "siteName",
          "xTitle",
          "xDescription",
          "xImage",
          "xCard",
          "warnings"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 1000
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          },
          "image": {
            "type": "string",
            "maxLength": 2048
          },
          "url": {
            "type": "string",
            "maxLength": 2048
          },
          "siteName": {
            "type": "string",
            "maxLength": 1000
          },
          "xTitle": {
            "type": "string",
            "maxLength": 1000
          },
          "xDescription": {
            "type": "string",
            "maxLength": 1000
          },
          "xImage": {
            "type": "string",
            "maxLength": 2048
          },
          "xCard": {
            "type": "string",
            "maxLength": 1000
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 220
            },
            "maxItems": 24
          }
        }
      },
      "SeoInsights": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "source",
          "scannedAt",
          "searchPreview",
          "wordCount",
          "language",
          "textTruncated",
          "keyPhrases"
        ],
        "properties": {
          "source": {
            "type": "string",
            "const": "page-source"
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time"
          },
          "searchPreview": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "title",
              "description",
              "canonical"
            ],
            "properties": {
              "title": {
                "type": "string",
                "maxLength": 1000
              },
              "description": {
                "type": "string",
                "maxLength": 1000
              },
              "canonical": {
                "type": "string",
                "maxLength": 2048
              }
            }
          },
          "wordCount": {
            "type": "integer",
            "minimum": 0
          },
          "language": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "textTruncated": {
            "type": "boolean"
          },
          "keyPhrases": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "phrase",
                "occurrences",
                "evidence"
              ],
              "properties": {
                "phrase": {
                  "type": "string",
                  "maxLength": 200
                },
                "occurrences": {
                  "type": "integer",
                  "minimum": 0
                },
                "evidence": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "title",
                      "description",
                      "heading",
                      "body"
                    ]
                  },
                  "uniqueItems": true
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error",
          "code",
          "returnedAt"
        ],
        "properties": {
          "error": {
            "type": "string",
            "maxLength": 500
          },
          "code": {
            "type": "string",
            "maxLength": 80,
            "enum": [
              "invalid_request",
              "invalid_api_key",
              "origin_not_allowed",
              "report_not_found",
              "request_too_large",
              "unsupported_media_type",
              "audit_unavailable",
              "request_rate_limited",
              "key_creation_limited",
              "scan_rate_limited",
              "audit_failed",
              "service_unavailable",
              "https_required",
              "query_not_allowed"
            ]
          },
          "returnedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Time the error response was assembled."
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "RateLimitInfo": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "requestsPerMinute",
          "scansPerMinute",
          "scansPerHour",
          "scansPerDay",
          "serviceRequestsPerDay"
        ],
        "properties": {
          "requestsPerMinute": {
            "type": "integer",
            "const": 60
          },
          "scansPerMinute": {
            "type": "integer",
            "const": 3
          },
          "scansPerHour": {
            "type": "integer",
            "const": 10
          },
          "scansPerDay": {
            "type": "integer",
            "const": 20
          },
          "serviceRequestsPerDay": {
            "type": "integer",
            "const": 10000,
            "description": "Shared service request ceiling per UTC day across all v1 calls; independent of the separate 500-new-scan cap."
          }
        },
        "description": "Per-key limits. All v1 calls additionally share 60 requests per fixed UTC minute per IP and 10,000 requests per UTC day service-wide; scans also use shared per-IP and service-wide limits."
      },
      "CreateKeyRequest": {
        "type": "object",
        "additionalProperties": false,
        "maxProperties": 0,
        "description": "Empty object; key creation requires no account and no authentication."
      },
      "KeyInfo": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "prefix",
          "createdAt",
          "expiresAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "prefix": {
            "type": "string",
            "minLength": 18,
            "maxLength": 18,
            "description": "First 18 characters of the generated key, matching key.slice(0, 18); display identifier only, never sufficient to authenticate.",
            "pattern": "^bare_live_[0-9a-f]{8}$"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thirty days after creation."
          }
        }
      },
      "CreatedKeyResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "key",
          "keyInfo",
          "limits"
        ],
        "properties": {
          "key": {
            "type": "string",
            "pattern": "^bare_live_[0-9a-f]{64}$",
            "minLength": 74,
            "maxLength": 74,
            "description": "Full secret returned once only. If created through the Bare developer webpage, it remains in page memory only. Server-to-server clients should store it in a server-side secret manager; never place it in a URL, log, or browser localStorage."
          },
          "keyInfo": {
            "$ref": "#/components/schemas/KeyInfo"
          },
          "limits": {
            "$ref": "#/components/schemas/RateLimitInfo"
          }
        }
      },
      "KeyInfoResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "keyInfo",
          "limits"
        ],
        "properties": {
          "keyInfo": {
            "$ref": "#/components/schemas/KeyInfo"
          },
          "limits": {
            "$ref": "#/components/schemas/RateLimitInfo"
          }
        }
      },
      "RevokeKeyResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "revoked"
        ],
        "properties": {
          "revoked": {
            "type": "boolean",
            "const": true
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed JSON, unsupported properties, invalid field type, HTTPS requirement, or query-string use. Codes: invalid_request, https_required, query_not_allowed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, expired, or revoked bearer key. Code: invalid_api_key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "illustrative": {
                "summary": "Illustrative authentication error; no credential is included.",
                "value": {
                  "error": "A valid API key is required.",
                  "code": "invalid_api_key",
                  "returnedAt": "2026-10-16T12:00:00Z"
                }
              }
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "Forbidden": {
        "description": "The request Origin is not allowed for this server-to-server API. Code: origin_not_allowed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "TooLarge": {
        "description": "Request body exceeds the 4 KiB limit. Code: request_too_large.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Request must use application/json. Code: unsupported_media_type.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "Unprocessable": {
        "description": "The target URL or audit cannot be processed safely. Code: audit_unavailable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "RateLimited": {
        "description": "429 responses use request_rate_limited for the shared 10,000-request UTC-day budget or the 60-per-minute IP/key request limits; key_creation_limited for key issuance limits; or scan_rate_limited for new-scan quotas, concurrency, and hostname cooldown. All include Retry-After. The service-day request limit is independent of the 500-new-scan daily cap; its Retry-After points to the next UTC midnight.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "illustrative": {
                "summary": "Illustrative rate-limit error and timestamp.",
                "value": {
                  "error": "The applicable request or scan limit has been reached.",
                  "code": "request_rate_limited",
                  "returnedAt": "2026-10-16T12:00:00Z"
                }
              },
              "keyCreation": {
                "summary": "Illustrative key issuance limit response",
                "value": {
                  "error": "Key creation limit reached.",
                  "code": "key_creation_limited",
                  "returnedAt": "2026-10-16T12:00:00Z"
                }
              },
              "scanLimit": {
                "summary": "Illustrative new-scan limit response",
                "value": {
                  "error": "Scan limit reached.",
                  "code": "scan_rate_limited",
                  "returnedAt": "2026-10-16T12:00:00Z"
                }
              }
            }
          }
        }
      },
      "UpstreamFailure": {
        "description": "The page or scan dependency failed after reservation. Code: audit_failed; raw exceptions and credentials are not returned.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "Unavailable": {
        "description": "API configuration or storage is temporarily unavailable. Code: service_unavailable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      },
      "NotFound": {
        "description": "No unexpired report exists for this ID. Code: report_not_found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "illustrative": {
                "summary": "Illustrative missing-report error and timestamp.",
                "value": {
                  "error": "Report not found.",
                  "code": "report_not_found",
                  "returnedAt": "2026-10-16T12:00:00Z"
                }
              }
            }
          }
        },
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "X-Robots-Tag": {
            "$ref": "#/components/headers/NoIndex"
          }
        }
      }
    },
    "examples": {
      "CompletedExample": {
        "summary": "Illustrative schema example using reserved example.com, not a live scan or report",
        "value": {
          "status": "partial",
          "id": "00000000-0000-4000-8000-000000000001",
          "measuredAt": "2026-10-16T11:58:00Z",
          "expiresAt": "2026-10-23T11:58:00Z",
          "returnedAt": "2026-10-16T12:00:00Z",
          "cacheHit": false,
          "profile": {
            "device": "mobile",
            "scope": "single-page"
          },
          "scoreInfo": {
            "bareScore": {
              "method": "bare-score-v1",
              "status": "unavailable",
              "score": null,
              "grade": null,
              "components": [
                {
                  "id": "performance",
                  "label": "Performance",
                  "score": null
                },
                {
                  "id": "seo",
                  "label": "SEO",
                  "score": null
                },
                {
                  "id": "accessibility",
                  "label": "Accessibility",
                  "score": null
                }
              ],
              "missing": [
                "performance",
                "seo",
                "accessibility"
              ]
            },
            "categories": [
              {
                "id": "performance",
                "score": null
              },
              {
                "id": "seo",
                "score": null
              },
              {
                "id": "accessibility",
                "score": null
              }
            ]
          },
          "report": {
            "id": "00000000-0000-4000-8000-000000000001",
            "url": "https://example.com/",
            "finalUrl": "https://example.com/",
            "hostname": "example.com",
            "generatedAt": "2026-10-16T11:58:00Z",
            "expiresAt": "2026-10-23T11:58:00Z",
            "device": "mobile",
            "status": "partial",
            "categories": [
              {
                "id": "seo",
                "label": "SEO",
                "score": null,
                "metrics": []
              }
            ],
            "findings": [],
            "resources": [],
            "thirdParties": [],
            "notices": [
              "Illustrative reserved-domain sample; no scan result is asserted."
            ],
            "labAvailable": false,
            "fieldAvailable": false,
            "engine": "illustrative-example",
            "bareScore": {
              "method": "bare-score-v1",
              "status": "unavailable",
              "score": null,
              "grade": null,
              "components": [
                {
                  "id": "performance",
                  "label": "Performance",
                  "score": null
                },
                {
                  "id": "seo",
                  "label": "SEO",
                  "score": null
                },
                {
                  "id": "accessibility",
                  "label": "Accessibility",
                  "score": null
                }
              ],
              "missing": [
                "performance",
                "seo",
                "accessibility"
              ]
            }
          }
        }
      },
      "CreatedKeyExample": {
        "summary": "Illustrative response; the zeroed secret is not a usable key",
        "value": {
          "key": "bare_live_0000000000000000000000000000000000000000000000000000000000000000",
          "keyInfo": {
            "id": "00000000-0000-4000-8000-000000000002",
            "prefix": "bare_live_00000000",
            "createdAt": "2026-10-16T12:00:00Z",
            "expiresAt": "2026-11-15T12:00:00Z"
          },
          "limits": {
            "requestsPerMinute": 60,
            "scansPerMinute": 3,
            "scansPerHour": 10,
            "scansPerDay": 20,
            "serviceRequestsPerDay": 10000
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://bare.io",
      "description": "Bare.io production API; HTTPS required."
    }
  ]
}
