Migrating from Perigon
Replace /v1/all with /v1/news/everything. Most parameters have a direct counterpart, which is exactly why the three that differ silently are the ones to fix first: paging is 0-indexed on Perigon and 1-indexed here, unquoted adjacent terms mean OR on Perigon and AND here, and the two sentiment models are not the same measurement.
Why teams switch
| Perigon | APITube | |
|---|---|---|
| Articles per request | 100 | 250 |
| Sentiment | Three probabilities per article | Signed score for title, body, overall, and per entity |
| Category taxonomy | 15 fixed names | IPTC MediaTopics — thousands of codes, plus topics and industries |
| Publisher metadata | Source name and domain | Domain, country, political bias, Open Page Rank |
| Article text | Full content | Full body and body_html, plus an extractive summary |
Your first request
# Perigon — page 0 is the first page
curl "https://api.goperigon.com/v1/all?q=tesla&language=en&page=0&size=100" \
-H "x-api-key: YOUR_KEY"# APITube — page 1 is the first page
curl "https://api.apitube.io/v1/news/everything?title=tesla&language.code=en&page=1&per_page=100" \
-H "X-API-Key: YOUR_API_KEY"The three traps
1. Paging is off by one, and APITube does not error. Perigon pages start at 0; APITube pages start at 1 and treat page=0 as page=1. Forward the number unchanged and your first two pages return identical articles with a 200 — verified: page=0 and page=1 return the same three ids, page=2 is the first that differs. Always send perigon_page + 1.
2. The default operator is inverted. Perigon reads q=Elon Musk as Elon OR Musk; APITube reads title=Elon Musk as Elon AND Musk. On the same corpus that is 33 123 articles versus 20 409 — a 38% drop with no error. Translate to query=title:(Elon OR Musk). Quoted phrases ("Elon Musk") mean the same thing on both and port unchanged.
3. Sentiment does not convert exactly. Perigon returns {positive, negative, neutral} summing to 1. APITube returns a signed score per axis. Use score ≈ positive − negative — so positiveSentimentFrom=0.6 becomes sentiment.overall.score.min=0.2 — then re-tune against real results. neutralSentimentFrom and neutralSentimentTo have no equivalent at all.
Parameter mapping
| Perigon | APITube | Note |
|---|---|---|
q | title or query | Headlines only, and the default operator inverts — see above |
title | title | Lossless |
content, desc, url | — | No body, description or URL search |
category | category.id | 13 of 15 names map to IPTC codes, max 3 |
excludeCategory | ignore.category.id | |
topic | topic.id | Different taxonomies — verify each value |
source | source.domain | Max 3, exact domain |
excludeSource | ignore.source.domain | |
sourceCountry | source.country.code | |
companyName | organization.name | Strip legal suffixes — Apple Inc returns 400 |
personName | person.name | Resolves against an index, not free text |
companyDomain | — | Different meaning — the company's site, not the publisher |
companyId, personWikidataId | — | APITube entity ids are its own |
byline | author.name | Real journalist names only; desk names return 400 |
language | language.code | No ru, no uk |
from / to | published_at.start / .end | |
addDateFrom, refreshDateFrom | — | Publication date only |
positiveSentimentFrom / To | sentiment.overall.score.min / .max | Approximate |
negativeSentimentFrom / To | sentiment.overall.score.max / .min | Approximate, sign inverts |
neutralSentimentFrom / To | — | No neutral axis |
showReprints=false | is_duplicate=0 | Reprint grouping is lost |
medium=Video | has_video=1 | "Has a video" ≠ "is a video" |
lat / lon / maxDistance | /v1/news/local lat / lng / radius | A different endpoint |
page | page | +1 |
size | per_page | 250 max; 10 on Free, 50 on Starter (400 ER0171 above) |
sortBy=date | sort.by=published_at | |
sortBy=relevance | — | Returns 500 with a search term; use sort.by=source.rank.opr |
The full table of every Articles parameter is in the migration kit.
What does not carry over
Body-text search. content=, desc= and url= have no equivalent, and APITube ignores unknown parameters silently — forwarding them returns the whole index with a 200. Search headlines and filter the returned body locally.
Wildcards. q=immuni* fails the same way: title=immuni* is accepted and returns the entire index. Expand into query=title:(immunity OR immunization OR immunology).
Relevance boosting. ^2 has no equivalent. Sent verbatim it becomes a literal search term.
Google Content Categories. taxonomy=/Finance/Banking and prefixTaxonomy use a hierarchy with no mechanical mapping to IPTC. Pick an IPTC code by hand.
Clustering endpoints. /v1/stories/all has no counterpart — every article carries story.id for client-side grouping, but there is no cluster endpoint and no clusterId filter. /v1/vector/news/all has none either: APITube has no embeddings. /v1/summarize is largely covered by the summary array already on every article.
Russian and Ukrainian. language.code=ru and uk return 400 ER0237. There is no substitute.
What you gain
250 articles per request instead of 100 (10 on Free, 50 on Starter), sentiment on the headline and the body separately plus a score towards each extracted entity, IPTC MediaTopics alongside topics and industries, publisher political bias and Open Page Rank, readability scores, and export to eight formats.
Next steps
- perigon-apitube-migration-kit — every Articles parameter mapped, all 13 category codes verified live, the query-syntax and sentiment-conversion references, a drop-in shim for Node.js and Python (101 tests), and an AI prompt for bulk conversion
- Parameters reference
- Common migration issues