{
  "openapi": "3.1.0",
  "info": {
    "title": "hawkcrawl news API",
    "version": "1.0.0",
    "description": "News search over Google News, returning the publisher's real URL and the article text as JSON.\n\nOne endpoint. Measured 2026-09-18 over 636 articles across thirteen subjects and four periods: 636 of 646 resolved, 488 of 636 returned full text, archive verified back to 1995.\n\n**You are charged only for what arrives.** An article that will not resolve, or whose text a publisher refuses, costs nothing.",
    "contact": {
      "name": "hawkcrawl",
      "url": "https://hawkcrawl.com",
      "email": "hello@hawkcrawl.com"
    },
    "termsOfService": "https://hawkcrawl.com/terms"
  },
  "servers": [
    {
      "url": "https://api.hawkcrawl.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Full reference",
    "url": "https://hawkcrawl.com/docs"
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key from https://api.hawkcrawl.com/keys, as `Authorization: Bearer hc_live_...`."
      }
    },
    "schemas": {
      "Article": {
        "type": "object",
        "required": [
          "id",
          "title",
          "source",
          "published_at",
          "url",
          "snippet"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier for the article, from the feed."
          },
          "title": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Publisher name as the feed gives it, e.g. \"Reuters\"."
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the publisher dated it \u2014 not when we found it."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The Google News link. Opaque and encoded; use resolved_url for the publisher's own."
          },
          "snippet": {
            "type": "string"
          },
          "resolved_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The publisher's real URL. Present only with resolve=true or content=true. Null when it could not be resolved, and then it is not charged."
          },
          "content": {
            "type": [
              "string",
              "null"
            ],
            "description": "The full article text. Present only with content=true. Null when the publisher would not give it up, and then it is not charged."
          },
          "content_status": {
            "type": "string",
            "enum": [
              "ok",
              "blocked",
              "paywalled",
              "unavailable"
            ],
            "description": "ok \u2014 text returned. blocked \u2014 the publisher refused us. paywalled \u2014 what came back was a teaser, not an article. unavailable \u2014 the page is gone or would not load."
          },
          "duplicates": {
            "type": "array",
            "description": "Other outlets that ran the same story. Present only with dedupe=true, and never charged for.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "source": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                }
              }
            }
          },
          "relevance": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "How well this answers the query. Present only with relevance=true. 1.0 directly about it, 0.7 a plausible read, 0.3 same field different story, 0.0 something else. Nothing is ever removed on this score."
          },
          "relevance_note": {
            "type": "string",
            "description": "A few words on why that score."
          },
          "related": {
            "type": "array",
            "description": "Related articles the feed grouped with this one.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "source": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/v1/news": {
      "get": {
        "operationId": "searchNews",
        "summary": "Search news",
        "description": "Costs 1 credit for the search, 4 per article resolved to its publisher, and 20 per article whose full text is returned. A search that finds nothing costs nothing.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "What to search for. Google search syntax works: quoted phrases, OR, -exclusions, site:.",
            "example": "tesla earnings"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "How many articles to return. The upstream ceiling is about 100 whatever you ask."
          },
          {
            "name": "when",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^\\d+[hdwmy]$"
            },
            "description": "Relative window, e.g. 1h, 7d, 30d, 1y. Cannot be combined with from/to.",
            "example": "7d"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Start of a date range, YYYY-MM-DD. Requires `to`. Verified as far back as 1995."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "End of a date range, YYYY-MM-DD. Requires `from`."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ar",
                "de",
                "en",
                "es",
                "fr",
                "hi",
                "id",
                "it",
                "ja",
                "ko",
                "nl",
                "pl",
                "pt",
                "ru",
                "sv",
                "tr"
              ],
              "default": "en"
            },
            "description": "Language edition. An unsupported code is rejected rather than silently answered in English."
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            },
            "description": "Two-letter country for the edition, e.g. US, DE, BR."
          },
          {
            "name": "resolve",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Resolve each article to the publisher's real URL. 4 credits each, only when it works."
          },
          {
            "name": "content",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Fetch the full article text. Implies resolve. 20 credits each, only when the text arrives."
          },
          {
            "name": "dedupe",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Collapse syndicated copies before anything is charged. About 18% of a result set is a story already in it."
          },
          {
            "name": "relevance",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Score each article 0-1 against the query. 5 credits for the whole set, and only when the scores arrive. Removes nothing."
          }
        ],
        "responses": {
          "200": {
            "description": "Results, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "query",
                    "count",
                    "credits_used",
                    "cached",
                    "articles"
                  ],
                  "properties": {
                    "query": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "credits_used": {
                      "type": "integer",
                      "description": "What this call actually cost."
                    },
                    "cached": {
                      "type": "boolean",
                      "description": "Whether the search came from cache. A cached search still costs its 1 credit; a resolve or extraction already paid for is never charged twice."
                    },
                    "articles": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Article"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad parameters."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "Not enough credits. The body carries what you hold and what the call needed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "insufficient_credits"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "balance": {
                      "type": "integer"
                    },
                    "required": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "error": "insufficient_credits",
                  "message": "This call needed 81 credits and you have 3. Top up at https://api.hawkcrawl.com/billing \u2014 credits never expire.",
                  "balance": 3,
                  "required": 81
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: 10 requests a second and 120 a minute per key. Retry-After says when.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Upstream unavailable. Nothing was charged."
          }
        }
      }
    }
  }
}
