{"openapi":"3.1.0","info":{"title":"HonestFetch API","version":"0.1","description":"Read a web page as compact Markdown for AI use. Every request is signed, obeys the site's rules, and keeps nothing afterwards.","contact":{"url":"https://honestfetch.dev/bot"}},"servers":[{"url":"https://honestfetch.dev"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key, `hf_…`."}},"schemas":{"ReadInput":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri"},"question":{"type":"string","minLength":1,"maxLength":1000},"max_tokens":{"default":1200,"type":"integer","minimum":1,"maximum":8000}},"required":["url","max_tokens"],"additionalProperties":false},"ReadResult":{"$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"type":"object","properties":{"status":{"type":"string","const":"ok"},"url":{"type":"string","maxLength":2048,"format":"uri"},"title":{"type":"string"},"content":{"type":"string"},"tokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"original_tokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"truncated":{"type":"boolean"},"via":{"type":"string","enum":["markdown","html","plain"]},"terms":{"type":"object","properties":{"ai_input":{"anyOf":[{"type":"string","enum":["yes","no"]},{"type":"null"}]},"license":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]}},"required":["ai_input","license"],"additionalProperties":false}},"required":["status","url","title","content","tokens","original_tokens","truncated","via","terms"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"blocked"},"reason":{"type":"string","enum":["robots","content_signal","license","opted_out","http_401","http_403","http_451","challenge"]},"human_link":{"type":"string","maxLength":2048,"format":"uri"}},"required":["status","reason","human_link"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"payment_required"},"price":{"type":["string","null"]},"currency":{"type":["string","null"]},"scheme":{"type":["string","null"]},"human_link":{"type":"string","maxLength":2048,"format":"uri"}},"required":["status","price","currency","scheme","human_link"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"needs_browser"},"human_link":{"type":"string","maxLength":2048,"format":"uri"}},"required":["status","human_link"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"busy"},"retry_after_s":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["status","retry_after_s"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"error"},"code":{"type":"string","enum":["too_large","timeout","unsupported_type","invalid_url","ssrf_blocked","too_many_redirects","not_found","origin_error","unauthorized","bad_request","rate_limited","quota_exceeded","policy_unavailable","internal"]},"retry_after_s":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["status","code"],"additionalProperties":false}]},"PreflightInput":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri"}},"required":["url"],"additionalProperties":false},"PreflightResult":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","enum":["allow","allow_with_terms","paid","blocked","unknown"]},"reason":{"type":"string","enum":["robots","content_signal","license","opted_out","http_401","http_403","http_451","challenge"]},"terms":{"type":"object","properties":{"ai_input":{"anyOf":[{"type":"string","enum":["yes","no"]},{"type":"null"}]},"license":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]}},"required":["ai_input","license"],"additionalProperties":false},"price":{"type":["string","null"]},"currency":{"type":["string","null"]},"human_link":{"type":"string","maxLength":2048,"format":"uri"}},"required":["status","terms","human_link"],"additionalProperties":false},"UsageSummary":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"from":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"to":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991},"tokens_returned":{"type":"integer","minimum":0,"maximum":9007199254740991},"tokens_original":{"type":"integer","minimum":0,"maximum":9007199254740991},"by_day":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["date","reads"],"additionalProperties":false}},"by_host":{"type":"array","items":{"type":"object","properties":{"host":{"type":"string"},"reads":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["host","reads"],"additionalProperties":false}}},"required":["from","to","reads","tokens_returned","tokens_original","by_day","by_host"],"additionalProperties":false},"OptOutInput":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253},"contact":{"type":"string","minLength":3,"maxLength":254},"method":{"type":"string","enum":["dns","email"]}},"required":["domain","contact","method"],"additionalProperties":false},"OptOutVerifyInput":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"domain":{"type":"string","minLength":1,"maxLength":253}},"required":["domain"],"additionalProperties":false},"OptOutResult":{"$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"type":"object","properties":{"status":{"type":"string","const":"pending"},"domain":{"type":"string"},"method":{"type":"string","enum":["dns","email"]},"record":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","const":"TXT"},"value":{"type":"string"}},"required":["name","type"],"additionalProperties":false},"next":{"type":"string"}},"required":["status","domain","method","next"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"verified"},"domain":{"type":"string"},"verified_at":{"type":"string"}},"required":["status","domain","verified_at"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"unverified"},"domain":{"type":"string"},"reason":{"type":"string","enum":["no_request","record_not_found","token_mismatch"]},"record":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","const":"TXT"},"value":{"type":"string"}},"required":["name","type"],"additionalProperties":false}},"required":["status","domain","reason","record"],"additionalProperties":false}]}}},"paths":{"/v1/read":{"post":{"operationId":"read","summary":"Read one page as compact Markdown.","description":"Pass `question` to get only the passages that bear on it. A `blocked`, `payment_required` or `needs_browser` answer is a 200: the call worked, and the answer is that the page cannot be returned. `human_link` is what a person can open.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadInput"}}}},"responses":{"200":{"description":"The answer, whatever it turned out to be.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"400":{"description":"The request was malformed, or named a URL HonestFetch will not use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"401":{"description":"No usable API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"403":{"description":"The customer is disabled."},"429":{"description":"A customer limit, or a politeness limit. `retry_after_s` says how long.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"502":{"description":"The origin answered with neither a page nor a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"503":{"description":"A policy source could not be read, so the read fails closed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"504":{"description":"The origin did not answer in time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}}}}},"/v1/preflight":{"post":{"operationId":"preflight","summary":"Ask whether a page may be read, on what terms, and at what price.","description":"Fetches robots.txt and any licence the site publishes, and never the page itself. `unknown` means a source could not be read, which is an answer and not an error.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreflightInput"}}}},"responses":{"200":{"description":"The decision and the terms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreflightResult"}}}},"400":{"description":"The request was malformed, or named a URL HonestFetch will not use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"401":{"description":"No usable API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"403":{"description":"The customer is disabled."},"429":{"description":"A customer limit, or a politeness limit. `retry_after_s` says how long.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}}}}},"/v1/optout":{"post":{"operationId":"optout","summary":"Ask for a domain to be excluded. Public: no API key.","description":"Returns a token to publish as a TXT record at `_<product>-optout.<domain>`, then call `/v1/optout/verify`. Nothing is excluded until ownership is proved. A domain already excluded is answered `verified` and its record is not touched.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptOutInput"}}}},"responses":{"200":{"description":"What to publish, or that it is already excluded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptOutResult"}}}},"400":{"description":"The domain is one the list cannot hold, or the address is not at the domain."}}}},"/v1/optout/verify":{"post":{"operationId":"optoutVerify","summary":"Check the TXT record and put the domain on the list. Public: no API key.","description":"Reads the record and matches it against the request. On a match the domain is refused from the very next call, before any request is made to the site. The token is never echoed back.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptOutVerifyInput"}}}},"responses":{"200":{"description":"Verified, or why it is not yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptOutResult"}}}},"400":{"description":"The domain is one the list cannot hold."}}}},"/v1/usage":{"get":{"operationId":"usage","summary":"The customer's own usage for the current period.","description":"Reads only. Counts and tokens, never a URL path and never page content (ARCHITECTURE section 10).","responses":{"200":{"description":"The summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummary"}}}},"401":{"description":"No usable API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResult"}}}},"403":{"description":"The customer is disabled."}}}}}}