hawkcrawl

API Reference

One endpoint. Send a query, get structured news back — from the last hour or from 2010.

Quickstart

Every account gets 5,000 free credits a month — 5,000 searches, or 250 articles with full text, or any mix. No card, and no delay on results: the free tier returns the same live articles the paid ones do.

# your first request
curl "https://api.hawkcrawl.com/v1/news?q=tesla" \
  -H "Authorization: Bearer hc_live_YOUR_KEY"

That is the whole integration. There is no SDK to install and no client to configure — but here is the same call in the two languages people actually use.

Python

import requests

r = requests.get(
    "https://api.hawkcrawl.com/v1/news",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={
        "q": "tesla earnings",
        "limit": 10,
        "content": "true",   # full article text
        "dedupe": "true",    # drop syndicated copies, uncharged
    },
    timeout=60,
)
r.raise_for_status()
data = r.json()

print(f"{data['count']} articles, {data['credits_used']} credits")
for a in data["articles"]:
    # content is None when the publisher refused us — and was not charged
    if a.get("content"):
        print(a["source"], "—", len(a["content"]), "chars")
    else:
        print(a["source"], "— no text:", a.get("content_status"))

JavaScript

const params = new URLSearchParams({
  q: "tesla earnings",
  limit: "10",
  content: "true",
  dedupe: "true",
});

const res = await fetch(`https://api.hawkcrawl.com/v1/news?${params}`, {
  headers: { Authorization: `Bearer ${process.env.HAWKCRAWL_KEY}` },
});

if (res.status === 402) {
  // the body says what you hold and what the call needed
  const { balance, required } = await res.json();
  throw new Error(`need ${required} credits, have ${balance}`);
}

const { articles, credits_used } = await res.json();
for (const a of articles) {
  // a.content is null when the publisher would not give it up
  console.log(a.source, a.content ? `${a.content.length} chars` : a.content_status);
}
Machine-readable

The full contract is at /openapi.json — OpenAPI 3.1, generated from the same route definition this page describes, so the two cannot drift. Import it into Postman or Insomnia, or generate a client from it.

Authentication

Pass your key as a bearer token. Keys are shown once at creation — store it then, because we only keep a hash.

Authorization: Bearer hc_live_YOUR_KEY

A key can also go in the key query parameter when a header is inconvenient, though the header is preferred so the key stays out of logs and browser history.

GET /v1/news

GET https://api.hawkcrawl.com/v1/news
ParameterTypeDefaultNotes
q requiredstringSearch phrase. Quotes and OR work as they do in a search box.
fromYYYY-MM-DDOldest article date. The archive reaches back to 2010.
toYYYY-MM-DDNewest article date. Must be on or after from.
when1h1yRelative window. Cannot be combined with from/to.
langISO 639-1enSee Languages.
countryISO 3166-1USWhich edition to read the news from.
limit1–10050Values above 100 are rejected — see the note below.
resolvebooleanfalseReturn real publisher URLs. Costs extra credits — see Resolving article URLs.
dedupebooleanfalseCollapse syndicated copies before anything is charged — see Syndicated duplicates.
relevancebooleanfalseScore each result 0-1 against your query. Removes nothing — see Relevance scores.
contentbooleanfalseReturn the article text. Implies resolve. See Article text.
Hard ceiling: 100 articles

A single query returns at most 100 articles, and there is no pagination past it. This is a limit of the upstream index, not a plan restriction — no tier lifts it. To cover more ground, split the query by date range: two calls for two months will each return up to 100.

The response

Every response is JSON. articles is ordered newest first.

{
  "query": "tesla",
  "count": 2,
  "credits_used": 1,
  "cached": false,
  "articles": [
    {
      "id": "CBMipAFBVV95cUxNWldfU2Jy...",
      "title": "Tesla’s Cybercab Launch Fizzles, Leaving Waymo as the Robotaxi Leader",
      "source": "Yahoo Finance",
      "published_at": "2026-09-15T20:14:02Z",
      "url": "https://news.google.com/rss/articles/CBMipAFBV...",
      "resolved_url": null
    },
    {
      "id": "CBMijwFBVV95cUxQQXFwOTFB...",
      "title": "Woman hit on Pacific Beach boardwalk by man driving a stolen Tesla",
      "source": "KCRA",
      "published_at": "2026-09-15T22:03:00Z",
      "url": "https://news.google.com/rss/articles/CBMijwFBV...",
      "resolved_url": null
    }
  ]
}

What we return, and what we don't

We would rather you find this out here than after you have written the integration.

FieldAlways presentWhat it is
idyesStable identifier. Use it to de-duplicate across calls.
titleyesHeadline, with the trailing publisher name stripped.
sourceyesPublisher name, e.g. Reuters.
published_atyesISO 8601, UTC.
urlyesRedirect link to the article.
resolved_urlonly with resolve=trueThe publisher's own URL.
contentonly with content=trueExtracted article text, or null when we could not get it.
content_statusonly with content=trueok, paywalled, blocked, or unavailable.
relatedrarelyOther coverage of the same story. Present on roughly 2% of articles.
No summaries, no images, no author

By default we return headlines and metadata. We do not return an article summary, an image URL, or an author — the upstream index does not carry them, and we will not invent fields we cannot fill.

Article text is available on request with content=true, but not for every article. Read Article text before you build on it.

Date ranges and the archive

Historical queries are ordinary queries with from and to. We have verified results at 2010, 2018, 2020, 2022, 2023, 2024 and 2025.

curl "https://api.hawkcrawl.com/v1/news?q=tesla&from=2024-01-01&to=2024-02-01" \
  -H "Authorization: Bearer hc_live_YOUR_KEY"
{
  "query": "tesla",
  "from": "2024-01-01",
  "to": "2024-02-01",
  "count": 2,
  "credits_used": 1,
  "cached": true,
  "articles": [
    {
      "id": "CBMilgFBVV95cUxPSlFxM1Z6...",
      "title": "I rented an EV from Hertz. I'm not surprised they're dumping 20,000 electric cars.",
      "source": "Business Insider",
      "published_at": "2024-01-11T08:00:00Z",
      "url": "https://news.google.com/rss/articles/CBMilgFBV...",
      "resolved_url": null
    },
    {
      "id": "CBMivAFBVV95cUxPSWJkYWJ6...",
      "title": "Tesla loses EV sales crown to BYD in Q4, despite annual sales record",
      "source": "Wards Auto",
      "published_at": "2024-01-03T08:00:00Z",
      "url": "https://news.google.com/rss/articles/CBMivAFBV...",
      "resolved_url": null
    }
  ]
}

Note "cached": true. A date range that has already passed cannot change, so historical queries are served from cache and answer in milliseconds. They cost the same credit either way.

For relative windows use when instead: when=1h, when=7d, when=1y. With neither parameter you get roughly the last five days.

Languages

Pass lang, and country when you want a different edition of the same language. Every row below was checked against the live API and returned articles from publishers in that language.

langDefault countryEdition
enUSEnglish — United States (default)
deDEGerman
frFRFrench
esMXSpanish — Latin American edition
ptBRPortuguese — Brazil
itITItalian
nlNLDutch
plPLPolish
svSESwedish
ruRURussian
trTRTurkish
arEGArabic
hiINHindi
jaJPJapanese
koKRKorean
idIDIndonesian

Anything else returns 400 invalid_request naming the codes we support. We would rather refuse than quietly hand you English and let you ship it.

Resolving article URLs

The url we return is a redirect, not the publisher's address. Pass resolve=true and we look up each article's real URL and return it in resolved_url.

curl "https://api.hawkcrawl.com/v1/news?q=tesla&limit=10&resolve=true" \
  -H "Authorization: Bearer hc_live_YOUR_KEY"

Billing: 1 credit for the search, plus 4 credits for each article resolved. A resolved URL is cached permanently, so the same article never costs twice.

Leave it off when you only need headlines — most monitoring and alerting use cases never need the publisher URL.

Speed: resolving is work we do per article. A cold limit=50 with resolve=true takes a few seconds; because resolved URLs are cached permanently, the same articles come back instantly afterwards. If you are polling a feed on a schedule, only the first call pays for it.

Article text

Pass content=true and we resolve the article, fetch the publisher's page, and return the body text in content. It implies resolve=true.

This is the whole article, not a preview. For comparison, NewsAPI's documentation says its content field "is truncated to 200 chars" — ours is the full body or an honest null.

curl "https://api.hawkcrawl.com/v1/news?q=tesla&limit=10&content=true" \
  -H "Authorization: Bearer YOUR_KEY"
Expect a null on about one article in four

Measured 18 September 2026 over 636 articles across thirteen subjects and four periods: 488 returned clean text — 77%. It is not a bug and it will not quietly improve much further. Refused requests are retried from a residential address, which is what takes this from 69% to 77%; three publishers still do not open — reuters.com answers 401 because it wants a subscription, nytimes.com answers 403 to almost everything, and finance.yahoo.com refuses the connection. The remainder are paywall teasers, video-first pages, and articles that no longer exist.

The rate depends far more on subject than on age: 55-85% across topics for this week's news, and higher in the archive (86% for June 2016) because old articles are rarely walled. Housing, sport and technology sit near the top; central-bank and climate coverage near the bottom.

When we cannot get the text we return content: null and a content_status telling you why. We do not charge for those. Write your integration to expect a null.

A short body is reported as paywalled rather than returned as content — a 400-character news article is a subscription teaser, and passing that to a model is worse than passing nothing. We would rather return you an honest null than something that reads like an article and isn't.

Billing: 20 credits per article whose text we actually returned, on top of 1 for the search and 4 per article resolved. Extracted text is cached for 30 days and a cached article is never billed twice.

Syndicated duplicates

Measured over 160 articles across eight queries on 18 September 2026: 18% of what a search returns is a story already in the same result set. Almost all of it is syndication — one article republished by content partners. "Prediction: Tesla Stock Will Be Worth This Much in 2031" came back from The Motley Fool, Yahoo Finance and AOL.ca, character for character.

For a retrieval pipeline that is worse than waste: the same claim three times in a context window makes a model more confident, not better informed. And at 20 credits an article, you paid three times for one story.

curl "https://api.hawkcrawl.com/v1/news?q=tesla&limit=20&content=true&dedupe=true" \
  -H "Authorization: Bearer YOUR_KEY"

The copies are collapsed before anything is resolved or extracted, so you are never charged for them. The article Google ranked highest is kept and the rest are listed against it:

{
  "title": "Prediction: Tesla Stock Will Be Worth This Much in 2031",
  "source": "The Motley Fool",
  "content": "...",
  "duplicates": [
    { "source": "Yahoo Finance", "title": "...", "url": "..." },
    { "source": "AOL.ca", "title": "...", "url": "..." }
  ]
}
It errs toward keeping

Matching is on title-token overlap at a threshold of 0.8. Looser settings were tried on the same 160 articles — 0.35 collapsed 14% but also merged three separate Sky Sports pages about three different clubs, because their titles share boilerplate.

The two mistakes are not equal. Missing a duplicate costs you credits, and your usage page shows it. Merging two real stories hides one of them and you never find out. So this misses some duplicates on purpose, and no language model is involved — token overlap catches syndication at zero cost and zero latency, and a model here would be slower and dearer for no gain.

Relevance scores

Google News is good on mainstream subjects and weaker on narrow ones. A search for semiconductor export controls returned, among real coverage, a SanDisk share-price page, a Goldman Sachs note about futures traders, and a market-report advert. Keyword matching cannot catch those — the word semiconductor is right there in each.

relevance=true adds a score from 0 to 1 and a short reason to every article:

{
  "title": "AT&T Inc Share Price - T, RNS News, Quotes",
  "relevance": 0,
  "relevance_note": "ticker page, not an article"
}
ScoreMeans
1.0Directly about the subject
0.7A plausible read — market reaction, analysis, background, an adjacent angle
0.3Same industry or region, a different story
0.0Something else entirely, or not an article: ticker pages, market-report adverts, index pages
It scores. It does not filter.

The first version removed low-scoring articles, and removing was the wrong design. Against hand-labelled cases the best configuration caught 5 of 5 genuine junk items and also threw away 2 of 4 articles that belonged — for a query about the UK housing market it dropped a story about a Japanese builder buying a UK housebuilder, which is exactly that.

So you get the number and you choose the threshold. A retrieval pipeline can demand 0.8; someone researching a subject can take everything and read the notes. We do not shrink a result set you are paying for on a judgement that is ours rather than yours.

Billing: 5 credits for the whole result set, not per article, and only when the scores actually arrive. If the model is slow or unreachable you get your articles unscored and are charged nothing for it. Expect it to add one to three seconds; it is off by default for that reason.

Credits and caching

A credit is a tenth of a cent, so a dollar buys a thousand — and more than a thousand when you top up more at once.

ActionCreditsPer 1,000
One search, whatever limit you ask for1$1
One article resolved to the publisher's URL4$4
One article whose full text we returned20$20
Scoring one result set for relevance (relevance=true)5$0.005 a call

Failed requests cost nothing: if we cannot reach the index, the query returns no articles, an article will not resolve, or a publisher refuses us the text, you are not charged. Given that roughly half of articles return no text, that is not a courtesy — write your integration against it.

5,000 free credits arrive every month and do not need a card. Bought credits do not expire at all: there is no subscription and no reset. Pricing has the volume rates and worked examples.

Query typeCached forWhy
Historical (to in the past)30 daysA finished date range cannot change.
Live (when=1h or no dates)3 minutesFreshness is the point.
Everything else15 minutes

Cached responses carry "cached": true and return in milliseconds. They still cost the one search credit — you received the answer — but a resolve or an extraction already paid for is never charged again.

Rate limits

10 requests per second and 120 per minute per key. Exceeding either returns 429 with a Retry-After header. Rate limits are per key, so splitting work across keys splits the limit rather than raising it.

Errors

Every error has the same shape, so you can branch on error and show message.

{
  "error": "insufficient_credits",
  "message": "You have 3 credits remaining. Top up at https://api.hawkcrawl.com/billing."
}
HTTPerrorMeaning
400invalid_requestA parameter is missing or malformed. message names it.
401unauthorizedKey missing, revoked, or wrong.
402insufficient_creditsAllowance exhausted. Nothing was charged.
429rate_limitedToo many requests. See Retry-After.
503upstream_unavailableWe could not reach the index. Nothing was charged — retry.
Retrying safely

429 and 503 are the only errors worth retrying. Back off exponentially and cap your attempts. Retrying a 400 or 401 will fail identically every time.