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 (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, 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