{
 "openapi": "3.1.0",
 "info": {
  "title": "Border API",
  "version": "1.0.0",
  "description": "Border checks a cross-origin API call the way the browser does: it sends the real preflight to the live API (or reads pasted curl output for localhost), applies the Fetch standard CORS rules as Chrome implements them, and returns the verdict, Chrome's exact error message, the failing step and the…",
  "contact": {
   "name": "Clawhard Studio",
   "url": "https://clawhard.com",
   "email": "support@clawhard.com"
  }
 },
 "servers": [
  {
   "url": "https://border.clawhard.com"
  }
 ],
 "paths": {
  "/api/v1/check_cors": {
   "post": {
    "operationId": "check_cors",
    "summary": "Check whether a browser would allow a cross-origin API call (CORS)",
    "description": "Use when the user hits a CORS error (\"blocked by CORS policy\", \"No 'Access-Control-Allow-Origin' header\", \"preflight\", \"Failed to fetch\" from a frontend) or is about to call a public API from browser code (React, Next.js, Vue, a browser extension page) and asks whether it will work (signals:…",
    "x-full-description": "Use when the user hits a CORS error (\"blocked by CORS policy\", \"No 'Access-Control-Allow-Origin' header\", \"preflight\", \"Failed to fetch\" from a frontend) or is about to call a public API from browser code (React, Next.js, Vue, a browser extension page) and asks whether it will work (signals: \"CORS\", \"works in Postman but not in the browser\", \"preflight 401/404\", \"Access-Control-Allow-Headers\", \"withCredentials\", \"credentials include\"). Sends the browser's own preflight (OPTIONS with Origin, Access-Control-Request-Method and -Headers) to the live API and, for GET and HEAD, the request itself with the Origin header, then applies the Fetch standard CORS rules as Chrome implements them. Returns verdict (allowed, blocked, preflight_passed, incomplete, same_origin, unreachable), the exact Chrome console error, which step fails (preflight or response) and why, whether a preflight is needed and why, the CORS headers the server sent, warnings (missing Vary: Origin, a server that echoes any origin with credentials, unreadable response headers, Authorization covered only by *), the exact response headers that fix it, and curl commands to reproduce. Example: {\"url\":\"https://api.example.com/v1/items\",\"origin\":\"http://localhost:3000\",\"method\":\"POST\",\"headers\":[\"Content-Type: application/json\",\"Authorization\"],\"credentials\":true}. Limits: public http/https APIs on ports 80 and 443 only (localhost, private and staging servers go through border_check_cors_headers with pasted curl output); never sends POST, PUT, PATCH or DELETE (those get the preflight checked only) and never sends header values for Authorization or keys; a server can answer differently to a real browser session. Returns: {summary, verdict: allowed|blocked|preflight_passed|incomplete|same_origin|unreachable, browser_error (the exact Chrome console message, or null), request:{url, origin, method, credentials, headers:[{name, value?, safelisted}], preflight_required, preflight_reasons, access_control_request_headers, same_origin, mixed_content}, preflight:{status, headers, ok, error?:{code,hint}, allow_methods, allow_headers, max_age_seconds}|null, response:{status, hops:[{url,status,origin_sent,headers,ok}], ok, error?, readable_headers, unreadable_headers}|null, problems:[{step,code,message}], warnings, fix:{preflight_response_headers, response_headers, notes}|null, notes, curl:{preflight?, request}, probe?, mode, engine, checked_at, open_url}",
    "x-openai-isConsequential": false,
    "responses": {
     "200": {
      "description": "Result. Always has \"ok\": true.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          }
         },
         "additionalProperties": true
        }
       }
      }
     },
     "400": {
      "description": "Bad input. {\"ok\":false,\"error\":\"...\"}"
     },
     "429": {
      "description": "Rate limited. Retry after the Retry-After header."
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "url",
         "origin"
        ],
        "properties": {
         "url": {
          "type": "string",
          "minLength": 4,
          "maxLength": 2000,
          "description": "The API address the page calls, for example \"https://api.example.com/v1/items\"."
         },
         "origin": {
          "type": "string",
          "minLength": 4,
          "maxLength": 300,
          "description": "The origin of the page that makes the request: scheme, host and port, for example \"https://app.example.com\" or \"http://localhost:5173\". A full page URL is reduced to its origin."
         },
         "method": {
          "type": "string",
          "minLength": 3,
          "maxLength": 20,
          "description": "HTTP method the page uses (default GET), for example \"POST\", \"PUT\", \"PATCH\" or \"DELETE\"."
         },
         "headers": {
          "type": "array",
          "maxItems": 30,
          "items": {
           "type": "string",
           "minLength": 1,
           "maxLength": 300
          },
          "description": "Request headers the page sets in fetch() or axios, as \"Name\" or \"Name: value\". Values matter only for Content-Type, Accept, Accept-Language, Content-Language and Range; give names alone for Authorization and API keys. Example: [\"Content-Type: application/json\",\"Authorization\"]."
         },
         "credentials": {
          "type": "boolean",
          "description": "true when the request sends cookies: fetch credentials: \"include\", XHR or axios withCredentials: true. Default false."
         },
         "read_headers": {
          "type": "array",
          "maxItems": 30,
          "items": {
           "type": "string",
           "minLength": 1,
           "maxLength": 100
          },
          "description": "Response headers the page code reads with response.headers.get(), for example [\"X-Total-Count\",\"ETag\"]. Checked against Access-Control-Expose-Headers."
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/check_cors_headers": {
   "post": {
    "operationId": "check_cors_headers",
    "summary": "Check pasted preflight and response headers against the CORS rules",
    "description": "Use when a CORS problem involves a server Border cannot reach (localhost, a dev or staging server, a private network, a custom port) or a POST, PUT, PATCH or DELETE whose real response needs checking, and the raw response headers are available from curl -i or the browser DevTools Network tab…",
    "x-full-description": "Use when a CORS problem involves a server Border cannot reach (localhost, a dev or staging server, a private network, a custom port) or a POST, PUT, PATCH or DELETE whose real response needs checking, and the raw response headers are available from curl -i or the browser DevTools Network tab (signals: \"here is the OPTIONS response\", \"my local API returns these headers\", \"CORS works on localhost but not in production\", \"preflight response is\"). Parses the pasted OPTIONS response and/or the actual response (status line plus headers; curl -i, curl -v, curl -iL with redirects, and DevTools copies are all read) and applies the Fetch standard CORS rules as Chrome implements them, for the given page origin, method, request headers and credentials mode. Returns the same result as border_check_cors: verdict, the exact Chrome console error, the failing step and reason, warnings, the response headers that fix it, and the curl commands to capture any missing response. Nothing is fetched. Example: {\"url\":\"http://localhost:8000/api/login\",\"origin\":\"http://localhost:5173\",\"method\":\"POST\",\"headers\":[\"Content-Type: application/json\"],\"credentials\":true,\"preflight_response\":\"HTTP/1.1 200 OK\\nAccess-Control-Allow-Origin: *\\nAccess-Control-Allow-Headers: content-type\"}. Limits: the verdict is only as complete as the pasted headers; a preflighted request needs both pastes for a full answer. Returns: {summary, verdict: allowed|blocked|preflight_passed|incomplete|same_origin|unreachable, browser_error (the exact Chrome console message, or null), request:{url, origin, method, credentials, headers:[{name, value?, safelisted}], preflight_required, preflight_reasons, access_control_request_headers, same_origin, mixed_content}, preflight:{status, headers, ok, error?:{code,hint}, allow_methods, allow_headers, max_age_seconds}|null, response:{status, hops:[{url,status,origin_sent,headers,ok}], ok, error?, readable_headers, unreadable_headers}|null, problems:[{step,code,message}], warnings, fix:{preflight_response_headers, response_headers, notes}|null, notes, curl:{preflight?, request}, probe?, mode, engine, checked_at, open_url}",
    "x-openai-isConsequential": false,
    "responses": {
     "200": {
      "description": "Result. Always has \"ok\": true.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          }
         },
         "additionalProperties": true
        }
       }
      }
     },
     "400": {
      "description": "Bad input. {\"ok\":false,\"error\":\"...\"}"
     },
     "429": {
      "description": "Rate limited. Retry after the Retry-After header."
     }
    },
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "url",
         "origin"
        ],
        "properties": {
         "url": {
          "type": "string",
          "minLength": 4,
          "maxLength": 2000,
          "description": "The API address the page calls, for example \"https://api.example.com/v1/items\"."
         },
         "origin": {
          "type": "string",
          "minLength": 4,
          "maxLength": 300,
          "description": "The origin of the page that makes the request: scheme, host and port, for example \"https://app.example.com\" or \"http://localhost:5173\". A full page URL is reduced to its origin."
         },
         "method": {
          "type": "string",
          "minLength": 3,
          "maxLength": 20,
          "description": "HTTP method the page uses (default GET), for example \"POST\", \"PUT\", \"PATCH\" or \"DELETE\"."
         },
         "headers": {
          "type": "array",
          "maxItems": 30,
          "items": {
           "type": "string",
           "minLength": 1,
           "maxLength": 300
          },
          "description": "Request headers the page sets in fetch() or axios, as \"Name\" or \"Name: value\". Values matter only for Content-Type, Accept, Accept-Language, Content-Language and Range; give names alone for Authorization and API keys. Example: [\"Content-Type: application/json\",\"Authorization\"]."
         },
         "credentials": {
          "type": "boolean",
          "description": "true when the request sends cookies: fetch credentials: \"include\", XHR or axios withCredentials: true. Default false."
         },
         "read_headers": {
          "type": "array",
          "maxItems": 30,
          "items": {
           "type": "string",
           "minLength": 1,
           "maxLength": 100
          },
          "description": "Response headers the page code reads with response.headers.get(), for example [\"X-Total-Count\",\"ETag\"]. Checked against Access-Control-Expose-Headers."
         },
         "preflight_response": {
          "type": "string",
          "minLength": 1,
          "maxLength": 20000,
          "description": "The raw OPTIONS (preflight) response: status line and headers, as printed by curl -i -X OPTIONS (see the curl.preflight command in any result) or copied from DevTools."
         },
         "response": {
          "type": "string",
          "minLength": 1,
          "maxLength": 20000,
          "description": "The raw response to the actual request: status line and headers from curl -i (curl -iL output with redirects is read hop by hop) or DevTools. Bodies are ignored."
         }
        }
       }
      }
     }
    }
   }
  }
 },
 "externalDocs": {
  "description": "llms.txt",
  "url": "https://border.clawhard.com/llms.txt"
 }
}