One endpoint. Send a query, get structured news back — from the last hour or from 2010.
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.
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"))
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);
}
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.
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.
| Parameter | Type | Default | Notes |
|---|---|---|---|
q required | string | — | Search phrase. Quotes and OR work as they do in a search box. |
from | YYYY-MM-DD | — | Oldest article date. The archive reaches back to 2010. |
to | YYYY-MM-DD | — | Newest article date. Must be on or after from. |
when | 1h … 1y | — | Relative window. Cannot be combined with from/to. |
lang | ISO 639-1 | en | See Languages. |
country | ISO 3166-1 | US | Which edition to read the news from. |
limit | 1–100 | 50 | Values above 100 are rejected — see the note below. |
resolve | boolean | false | Return real publisher URLs. Costs extra credits — see Resolving article URLs. |
dedupe | boolean | false | Collapse syndicated copies before anything is charged — see Syndicated duplicates. |
relevance | boolean | false | Score each result 0-1 against your query. Removes nothing — see Relevance scores. |
content | boolean | false | Return the article text. Implies resolve. See Article text. |
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.
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
}
]
}
We would rather you find this out here than after you have written the integration.
| Field | Always present | What it is |
|---|---|---|
id | yes | Stable identifier. Use it to de-duplicate across calls. |
title | yes | Headline, with the trailing publisher name stripped. |
source | yes | Publisher name, e.g. Reuters. |
published_at | yes | ISO 8601, UTC. |
url | yes | Redirect link to the article. |
resolved_url | only with resolve=true | The publisher's own URL. |
content | only with content=true | Extracted article text, or null when we could not get it. |
content_status | only with content=true | ok, paywalled, blocked, or unavailable. |
related | rarely | Other coverage of the same story. Present on roughly 2% of articles. |
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.
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.
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.
lang | Default country | Edition |
|---|---|---|
en | US | English — United States (default) |
de | DE | German |
fr | FR | French |
es | MX | Spanish — Latin American edition |
pt | BR | Portuguese — Brazil |
it | IT | Italian |
nl | NL | Dutch |
pl | PL | Polish |
sv | SE | Swedish |
ru | RU | Russian |
tr | TR | Turkish |
ar | EG | Arabic |
hi | IN | Hindi |
ja | JP | Japanese |
ko | KR | Korean |
id | ID | Indonesian |
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.
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.
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"
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.
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": "..." }
]
}
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.
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"
}
| Score | Means |
|---|---|
1.0 | Directly about the subject |
0.7 | A plausible read — market reaction, analysis, background, an adjacent angle |
0.3 | Same industry or region, a different story |
0.0 | Something else entirely, or not an article: ticker pages, market-report adverts, index pages |
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.
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.
| Action | Credits | Per 1,000 |
|---|---|---|
One search, whatever limit you ask for | 1 | $1 |
| One article resolved to the publisher's URL | 4 | $4 |
| One article whose full text we returned | 20 | $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 type | Cached for | Why |
|---|---|---|
Historical (to in the past) | 30 days | A finished date range cannot change. |
Live (when=1h or no dates) | 3 minutes | Freshness is the point. |
| Everything else | 15 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.
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.
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."
}
| HTTP | error | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed. message names it. |
| 401 | unauthorized | Key missing, revoked, or wrong. |
| 402 | insufficient_credits | Allowance exhausted. Nothing was charged. |
| 429 | rate_limited | Too many requests. See Retry-After. |
| 503 | upstream_unavailable | We could not reach the index. Nothing was charged — retry. |
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.