Migrating from the GDELT DOC 2.0 API
Replace api.gdeltproject.org/api/v2/doc/doc with /v1/news/everything. Unlike every other guide in this section, both sides of every claim below were executed rather than read — GDELT needs no key, so its behaviour could be measured directly.
These are not the same kind of product
GDELT is a free research dataset. APITube is a commercial news API. The difference decides whether you should migrate at all.
| GDELT DOC 2.0 | APITube | |
|---|---|---|
| Key required | No | Yes |
| Cost | Free | Paid, free tier available |
| Rate limit | Effectively a handful per hour — see below | 50/minute paid, 10/minute free |
| Records per request | 250 | 250 |
| Paging | None | page |
| Search scope | Full article text, 65+ languages | Headlines |
| Article body | Not returned | body and body_html |
| Fields per article | 8 | Dozens |
| Errors | HTTP 200 with a plain-text body | JSON with a status and an error code |
Move to APITube for: the article text, commercial licensing, entity extraction, sentiment, and a rate limit that supports production traffic.
Stay on GDELT for: 65-language full-text reach, the GKG themes taxonomy, and tone timelines — on a zero budget. Nothing here replaces GDELT's global research coverage.
Your first request
# GDELT — no key needed
curl "https://api.gdeltproject.org/api/v2/doc/doc?query=tesla&mode=artlist&format=json&maxrecords=250"# APITube
curl "https://api.apitube.io/v1/news/everything?title=tesla&per_page=250" \
-H "X-API-Key: YOUR_API_KEY"GDELT reports errors with HTTP 200
This is the trap most likely to be hiding in your current code. Ask for more than the maximum:
curl "https://api.gdeltproject.org/api/v2/doc/doc?query=tesla&mode=artlist&format=json&maxrecords=251"
# HTTP 200
# A maximum of 250 records can be returned.Status 200, body is prose. A client that checks the status and then parses JSON passes the first test and throws on the second. Rate limiting behaves identically — the body is a sentence, not a JSON error.
If a try/except around JSON parsing treats a parse failure as "no results", it has been silently swallowing both conditions. APITube returns 400 ER0171 with a structured error instead, so that code path can go — but look at what it was hiding first.
The rate limit is the real migration cost
GDELT's throttle message says "one request every 5 seconds". Measured on 27 July 2026:
| Interval between requests | Requests that got through |
|---|---|
| 6 seconds | 1 of 7 |
| 16 seconds | 4 of 12 |
| 60 seconds | 3 of 8 |
| 150 seconds | 0 of 3 |
| 300 seconds, over 35 minutes | 0 of 7 |
Backing off made it worse. Roughly 60 requests spread over 90 minutes was enough to trigger a block that no useful retry interval cleared, and a quiet period that cleared it once failed to clear it later in the same session.
The instinct on 429 — retry more slowly — is exactly what keeps you blocked here. APITube's 429 is a real status code against a documented per-minute budget, so backoff behaves normally.
For a backfill job, this ratio rather than the field mapping is what changes your runtime.
Parameter mapping
| GDELT | APITube | Note |
|---|---|---|
query (bare terms) | title | Full text → headlines |
query domain:X | source.domain=X | Exact |
query sourcelang:english | language.code=en | Name → ISO code |
query sourcecountry:US | source.country.code=us | Name → ISO code |
query theme:X | — | No GKG taxonomy |
maxrecords | per_page | Both cap at 250 |
mode=artlist | (the default) | |
mode=timelinevol | /v1/news/trends | Different endpoint and shape |
mode=tonechart | — | Compute from sentiment.overall.score |
| — | page | GDELT has no paging at all |
The full table is in the migration kit, including the parameters it deliberately leaves unmapped because they could not be verified.
Names become codes, and that breaks naive ports
GDELT emits English names; APITube takes ISO codes.
GDELT: "language": "Serbian", "sourcecountry": "Serbia"
APITube: language.code=sr source.country.code=rsForwarding the name fails, and the error tells you which mistake you made:
| What you send | Response |
|---|---|
language.code=Serbian | 400 ER0061 — must be 1–2 characters |
language.code=zz | 400 ER0237 — not found |
source.country.code=Serbia | 400 ER0246 — must be 2 characters |
source.country.code=zz | 400 ER0212 — not found |
A length error means a GDELT value was forwarded unconverted — a bug in your mapping code. A not-found error means your table produced a code APITube does not carry — a real coverage gap. Note also that the United Kingdom is gb, not uk.
Russian and Ukrainian are absent
language.code=ru and =uk return 400 ER0237; source.country.code=ru returns 400 ER0212. GDELT covers both. There is no substitute and no partial coverage.
Measure your own exposure before committing: run your real query on GDELT and count the "Russian" rows.
The language mix will shift
One GDELT response of 250 articles for query=tesla carried 15 languages: 44% Chinese, 36% English, 5% Spanish. APITube's index holds roughly 168 times more English than Chinese (18 313 566 against 109 095).
Those measure different things — a query result against index composition — so do not subtract them. But the direction is unambiguous: a query GDELT answered mostly in Chinese comes back mostly in English, and both APIs return 200 while it happens.
Before the cutover, compare the language histogram, not just the count.
Other things that will bite
No paging on GDELT. maxrecords returns up to 250 and stops — no offset, no cursor. Queries that matched more were worked around by slicing the date range. On APITube that workaround is unnecessary: use page.
seendate is not published_at. GDELT records when it first saw the article; APITube records publication time. For a late pickup these differ, and nothing reconciles them.
Date formats differ. GDELT writes 20260709T120000Z (ISO 8601 basic); APITube's filters take 2026-07-09T12:00:00Z (extended).
Multi-value filters apply the first 3. A fourth domain or language is dropped with no error.
What you gain
The article text itself — so whatever scrapes url today can be deleted, along with its retries, user-agent handling and failure modes. Plus named entities with Wikidata links and per-entity sentiment, sentiment on headline and body separately, IPTC MediaTopics alongside topic and industry axes, publisher political bias and Open Page Rank, readability scores, cross-publisher deduplication via is_duplicate and story.id, and export to eight formats.
Next steps
- gdelt-apitube-migration-kit — every verified parameter mapped and the unverified ones listed as such, the language and country tables with live counts, a drop-in shim for Node.js and Python (29 tests each, byte-identical output), and an AI prompt that refuses queries it cannot convert
- Parameters reference
- Common migration issues