La interfaz está localizada; las descripciones y documentos de las Skills se traducen automáticamente con IA a su idioma. El código, los comandos, rutas y términos técnicos se mantienen en el original, y prevalece el documento del autor.
Explorar Skills
Datos e investigación · SKILL.md

apitube-news-api

apitube-news-api

Query the APITube News API — authenticate, search and filter worldwide news articles by keyword, entity, sentiment, source, country and date, and parse the JSON response. Use when an agent needs live or historical news data, media monitoring, or news for a RAG pipeline.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentación de la Skill

GitHub

name: apitube-news-api description: Query the APITube News API — authenticate, search and filter worldwide news articles by keyword, entity, sentiment, source, country and date, and parse the JSON response. Use when an agent needs live or historical news data, media monitoring, or news for a RAG pipeline. license: MIT

APITube News API

APITube is a worldwide News API. Use it to search and filter news articles in real time or historically, then read structured fields (title, body, source, sentiment, entities).

Authentication

Read the key from the environment variable APITUBE_API_KEY and reference it as $APITUBE_API_KEY. Never write the key itself into a command, a file or a code sample: a literal key ends up in shell history, logs and version control. If the variable is unset, ask the user to export it — do not ask them to paste the key into the chat.

# 1. Authorization header (most secure)
curl -H "Authorization: Bearer $APITUBE_API_KEY" \
  "https://api.apitube.io/v1/news/everything?per_page=10"

# 2. X-API-Key header
curl -H "X-API-Key: $APITUBE_API_KEY" \
  "https://api.apitube.io/v1/news/everything?per_page=10"

# 3. Query parameter — accepted, but it leaks the key into server logs,
#    browser history and referrer headers. Use a header instead.
curl "https://api.apitube.io/v1/news/everything?per_page=10&api_key=$APITUBE_API_KEY"

Keys prefixed api_test_ run against the same data and do not consume quota, so use one while wiring up an integration. Only body and body_html are truncated, and the cut is marked with [Test mode — use a live key for full content]; title, description, entities, sentiment, categories and source come through in full. The Free plan truncates the same two fields, with the marker [Upgrade subscription plan]. Check for those strings to detect a truncated body instead of guessing from its length. See https://docs.apitube.io/platform/news-api/authentication.

Endpoints

EndpointMethodsPurpose
/news/everythingGET · POSTSearch all articles with full filtering
/news/top-headlinesGET · POSTBreaking and important news from sources with OPR rank >= 5
/news/countGET · POSTNumber of matching articles only — one count(*), no article payload
/news/articleGET · POSTA single article — pass id=<articleId>, not article.id (400 ER0232 without it)
/news/story/{articleId}GET · POSTThe cluster of articles covering the same story
/news/category/{taxonomy}/{categoryId} · /news/topic/{topicId} · /news/industry/{industryId} · /news/entity/{entityId}GET · POSTArticles by taxonomy
/news/trendsGET · POSTAggregations: top values of a field, growth, time buckets
/news/localGET · POSTGeo search — lat + lng + radius in km
/news/rawGET · POSTUnenriched articles: id, title, href, created_at, description, body, body_html, author, keywords, source only — no sentiment, entities or taxonomy
/news/event-typesGET onlyThe 44 classified event types and their categories
/people · /companies · /journalistsGET onlyReference profiles, not articles: id, name, links, profile (outlets for journalists)
/suggest/entities · /suggest/categories · /suggest/topics · /suggest/industriesGET onlyAutocomplete an ID from a name prefix
/balanceGET onlyRemaining quota for the key
/news/streamGET onlyLive Server-Sent Events feed

The article and aggregation endpoints take the same parameters either as a query string on GET or as a JSON body on POST — use POST when a long filter set would blow past URL length limits. The endpoints marked GET-only answer 404 to a POST, not 405, so a wrong method looks like a wrong path.

/news/stream answers 200 with content-type: text/event-stream and takes the same filters. Between matches it emits SSE comment lines — the literal bytes : heartbeat\n\n — not events. A parser that assumes every frame is event:/data: will choke on them, so drop lines beginning with :. A stream whose filters match nothing sends only heartbeats and delivers no articles. Streaming is billed per delivered article, so narrow the filters rather than opening a second connection.

Key parameters for /news/everything

Filters combine with AND. Comma-separated values inside one parameter combine with OR.

The per-parameter value cap is enforced by silent truncation, not by an error. Most list filters take at most 3 comma-separated values; event.type and article.id take 5. Pass more and the extras are dropped with no warning — the request still returns 200:

entity.id=1297647                              → 126 957
entity.id=1580517,474,1223812                  →   2 110
entity.id=1580517,474,1223812,1297647          →   2 110   ← 4th ignored
entity.id=1297647,1580517,474,1223812          → 127 040   ← now the 4th is the ignored one

event.type=ipo,layoffs,bankruptcy,recall,data-breach              → 26 511
event.type=ipo,layoffs,bankruptcy,recall,data-breach,earthquake   → 26 511   ← 6th ignored
event.type=earthquake                                             →  9 536   ← it does match on its own

Only the first N are applied, where N is that parameter's cap, so order decides which value you lose. Split larger sets across several requests and merge on article id.

  • title — keyword search in article titles, 2–100 chars; outside that range it is 400 ER0007, whose message reads "between 1 and 100 characters" but rejects a single character anyway. Quotes for an exact phrase, "climate change"~2 for proximity. Percent- encode the value. Title search is limited to a 31-day published_at window: with no dates the last 31 days are searched (200 plus warning ER0366 in meta.warnings); an explicit wider range returns 400 ER0110. Variants: title_starts_with, title_ends_with, title_pattern.
  • published_at.start, published_at.end — YYYY-MM-DD, ISO 8601, or Solr-style date math: NOW-7DAY, NOW-1WEEK, NOW/DAY, NOW-1DAY/DAY. Relative values are resolved per request, so a stored query never goes stale.
  • language.code — ISO 639-1, up to 3 comma-separated (en, en,fr,de).
  • category.id, topic.id, industry.id, entity.id — taxonomy IDs, up to 3 each. Resolve them with /suggest/* first — do not guess. The ID types differ: entities and industries are integers (1580517), topics are dotted slugs (industry.technology_news), categories are IPTC MediaTopic codes (medtop:04000000). A guessed value returns 400 ER0208, not an empty result — topic.id=technology, for instance, does not exist.
  • person.name, organization.name, location.name, brand.name, event.name, disease.name, disaster.name, sport.name — filter by entity name instead of an ID. The name must match the dictionary exactly, and a miss is a 400 error, not an empty result: location.name=New York → ER0218, brand.name=iPhone → ER0222, sport.name=football → ER0250, event.name=Olympics → ER0228, disaster.name=flood → ER0224. person.name=Elon Musk and organization.name=Google do resolve. Confirm a name with /suggest/entities before relying on it, or filter by entity.id.
  • source.domain, source.id, source.country.code, source.bias (left, center, right), source.rank.opr.min / .max — Open PageRank authority. In practice the data tops out at 8: over a two-day window min=6 matched 39 431 articles, min=7 matched 3 940, min=8 matched 80, and min=9 matched none. Use 5 for "mainstream", 7 for "major outlet"; asking for 9+ silently returns nothing.
  • sentiment.overall.polarity — positive and negative work; neutral currently matches nothing, because neutral articles are stored without a polarity value even though their responses show "polarity": "neutral". For neutral coverage use a score band instead: sentiment.overall.score.min=-0.1&sentiment.overall.score.max=0.1. The score filters (sentiment.overall.score.min / .max, range −1…1) are reliable in all three cases, and sentiment.title.* / sentiment.body.* behave the same way.
  • is_breaking, is_duplicate, is_high_quality, has_author, has_image, has_video, is_premium_source, is_verified_source, is_clickbait — all verified to filter. is_paywall is dead: is_paywall=1 returns zero articles across the whole index, and is_paywall=0 returns everything, so it cannot be used to avoid paywalled links.
  • event.type — one of 44 classified event types (ipo, layoffs, funding-round, data-breach, earthquake, …), up to 5; event.category is business, society or environment.
  • sort.by — published_at (default), relevance, engagement, quality, controversy, trust, source.rank.opr, sentiment.overall.score, read_time, and more. sort.order is asc or desc.
  • page, per_page — pagination. The ceiling is set by your plan: 10 on Free, 50 on Starter, 250 above that. Exceeding it is a hard 400 ER0171 ("Limit is out of range. Your plan allows up to N results per page"), not a silent clamp — read N from the message to learn your cap.
  • fl — comma-separated list of fields to return, e.g. fl=id,title,published_at,source.domain. Cuts response size sharply on large pages.
  • article.id — fetch specific articles by ID through /news/everything, up to 5 comma-separated.
  • query — boolean search language with AND / OR / NOT and grouping, for expressions title cannot express: https://docs.apitube.io/platform/news-api/query-builder

Exclusions: only 21 filters have an ignore. twin

ignore.<filter> excludes instead of includes, but only these 21 exist:

ignore.title              ignore.language.code       ignore.category.id
ignore.topic.id           ignore.industry.id         ignore.entity.id
ignore.source.id          ignore.source.domain       ignore.source.country.code
ignore.source.bias        ignore.author.id           ignore.author.name
ignore.person.name        ignore.organization.name   ignore.location.name
ignore.brand.name         ignore.event.name          ignore.event.type
ignore.disaster.name      ignore.disease.name        ignore.sport.name

Anything else — ignore.is_breaking, ignore.has_image, ignore.sentiment.overall.polarity — is silently dropped, with no error and an unchanged result count. There is no negation for sentiment, media, readability, read-time, geo or the boolean flags; invert those by selecting the complement instead (has_image=0 rather than ignore.has_image=1).

A working exclusion changes the count by exactly the excluded set: with 71 857 matching articles and 9 from theclemsoninsider.com, ignore.source.domain=theclemsoninsider.com returns 71 848.

Example — English AI coverage from the last week, quality sources only:

curl -H "Authorization: Bearer $APITUBE_API_KEY" \
  'https://api.apitube.io/v1/news/everything?title=%22artificial%20intelligence%22&language.code=en&published_at.start=NOW-7DAY&source.rank.opr.min=5&sort.by=published_at&per_page=10'

URL-encode the value. A raw quote or space in title makes the server return an empty body rather than an error — "artificial intelligence" must be sent as %22artificial%20intelligence%22. If a request comes back with nothing at all, an unencoded character in the query string is the first thing to check.

Plain-language queries with prompt

prompt (3–500 chars) describes the search in words; the API translates it into the parameters above before running the query and returns what it used in meta.prompt (text, applied, ignored, cached, model). Explicit parameters always win over the prompt.

It is accepted on /news/everything, /news/top-headlines, /news/count, /news/trends, /news/local, /news/raw, /news/stream and the four taxonomy endpoints. On /news/story and /news/article it is silently ignored — no error, just an unfiltered result. On /news/raw most of what a prompt produces cannot apply either, since that endpoint only understands dates, sorting and pagination.

curl -H "Authorization: Bearer $APITUBE_API_KEY" \
  "https://api.apitube.io/v1/news/everything?prompt=Tesla+and+Elon+Musk+news+in+English+for+the+last+10+days&per_page=10"

It costs 2 extra points on a cache miss (translations are cached), and it requires the Basic plan or above — Free and Starter get 403 ER0706. Other errors: ER0800 (length out of range), ER0801 (translation service down), ER0802 (nothing usable in the wording).

Response shape

Envelope

Eleven keys, always the same:

{
  "status": "ok",
  "limit": 2,
  "path": "https://api.apitube.io/v1/news/everything?language.code=en&per_page=2",
  "page": 1,
  "has_next_pages": true,
  "next_page": "https://api.apitube.io/v1/news/everything?language.code=en&per_page=2&page=2",
  "has_previous_page": false,
  "previous_page": "",
  "export": { "json": "http://…&export=json", "xlsx": "…", "csv": "…", "tsv": "…",
              "xml": "…", "rss": "…", "parquet": "…", "jsonl": "…" },
  "request_id": "ad527b55-4083-4174-a0b6-0a0a1f7adc8b",
  "results": [ /* … */ ]
}

path, next_page and every export URL come back as http://, not https://. Rewrite the scheme before following one, or your pagination loop downgrades to plaintext HTTP. Safer still: keep your own base URL and just increment page while has_next_pages is true. previous_page is "" — an empty string, not null — on the first page.

On failure status is "not_ok" and the body carries an errors array instead of results: [{ "status": 400, "code": "ER0208", "message": "…", "links": { "about": "…" }, "timestamp": "…" }]. Branch on status, not on the HTTP code alone.

Article object

Every article carries the same 33 fields — the set does not vary between articles. Abridged, with the real shapes:

{
  "id": 3072206097,
  "href": "https://gulfnews.com/world/asia/india/…",
  "published_at": "2026-08-15T06:39:23.000Z",
  "title": "UAE-India: A generational partnership for a shared future",
  "description": "On the auspicious occasion of the 80th Independence Day…",
  "body": "…plain text…",
  "body_html": "<div><div><p>…</p></div></div>",
  "language": "en",
  "translations": { "en": { "title": null, "description": null } },
  "author": { "id": 20059328, "name": "A GN Focus Report" },
  "image": "https://media.assettype.com/…",
  "categories": [ { "id": 268, "name": "emerging market", "score": 0.8,
                    "taxonomy": "iptc_mediatopics",
                    "links": { "self": "…/news/category/iptc_mediatopics/medtop:20001248" } } ],
  "topics":     [ { "id": "industry.green_energy_news", "name": "Green Energy Industry News",
                    "score": 0.9, "links": { "self": "…" } } ],
  "industries": [ { "id": 444, "name": "Credit Unions", "links": { "self": "…" } } ],
  "entities":   [ { "id": 1751049, "name": "IHC (International Holding Company)",
                    "type": "organization", "frequency": 1,
                    "links": { "self": "…", "wikipedia": "…", "wikidata": "…" },
                    "sentiment": { "score": 0, "polarity": "neutral",
                                   "mentions": { "positive": 0, "neutral": 1, "negative": 0 } },
                    "title": { "pos": [] },
                    "body":  { "pos": [ { "start": 4510, "end": 4539 } ] },
                    "metadata": { "type": "business", "subtype": "company", "aliases": [],
                                  "country": { "code": "AE", "name": "United Arab Emirates" },
                                  "founded": { "year": 1998 }, "description": "…" } } ],
  "locations_mentioned": [ { "name": "Abu Dhabi", "country": "AE",
                             "lat": 24.45111, "lng": 54.39694, "type": "city" } ],
  "source": { "id": 9186, "domain": "gulfnews.com",
              "home_page_url": "https://gulfnews.com", "type": "news", "bias": "left",
              "rankings": { "opr": 6 },
              "location": { "country_name": "United Arab Emirates", "country_code": "ae" },
              "favicon": "https://www.google.com/s2/favicons?domain=https://gulfnews.com" },
  "sentiment": { "overall": { "score": 0.11, "polarity": "positive" },
                 "title":   { "score": 0.13, "polarity": "positive" },
                 "body":    { "score": 0.09, "polarity": "positive" } },
  "summary": [ { "sentence": "…", "sentiment": { "score": 0.14, "polarity": "positive" } } ],
  "keywords": [], "links": [], "media": [],
  "readability": { "flesch_kincaid_grade": 17, "flesch_reading_ease": 17.9,
                   "automated_readability_index": 17.5, "difficulty_level": "expert",
                   "target_audience": "academic", "reading_age": 22,
                   "avg_words_per_sentence": 25, "avg_syllables_per_word": 1.9 },
  "story":  { "id": 3072206097, "uri": "https://api.apitube.io/v1/news/story/3072206097" },
  "shares": { "total": 6, "facebook": 3, "twitter": 2, "reddit": 1 },
  "is_duplicate": false, "is_free": true, "is_breaking": false,
  "read_time": 8, "sentences_count": 42, "paragraphs_count": 1,
  "words_count": 1049, "characters_count": 7163
}

Notes that matter when you parse this:

  • entities[] carries per-entity sentiment (score, polarity, and a mentions breakdown) and frequency, the mention count. This is the field to read when you need "how did this article talk about this company", as opposed to the article-level sentiment.
  • Do not use entities[].body.pos to highlight text. The start/end offsets are measured against an upstream version of the article that the API does not return; they line up with neither body nor body_html, and the drift is not a constant you can correct for (4, 7, 19, 23, 53 characters across entities in a single article). Some entities have offsets while their name does not appear in body at all. Locate mentions by searching body yourself.
  • image is a plain string URL, while media is a separate array — an article can have an image and an empty media. When there is no image the value is "", never null.
  • author is always an object, never a string and never null. With no byline it is {"id": null, "name": ""} — test author.name, not author, or every anonymous article looks attributed.
  • translations.en.{title,description} are null for articles already in English.
  • keywords, links and media are empty arrays roughly a third of the time, and topics is empty for about half of all articles. entities and categories are reliably populated. Do not treat an empty array as a parsing failure.
  • story.uri is a ready-made link to /news/story/{articleId} for this article. story.id is not a cluster key — it always equals the article's own id, including for articles returned together by /news/story. You cannot group articles by comparing story.id; call /news/story/{articleId} and use the returned set.

The article href points at the original publisher — link there, do not present the body as your own.

Do not feed response values straight back into filters

Filter parameters and response fields are not always spelled the same, and for categories they do not even hold the same value.

You filter onIt comes back as
source.rank.opr.minsource.rankings.opr
category.id=medtop:20001248 (IPTC code)categories[].id = 11 (internal integer)
sentiment.overall.polaritysentiment.overall.{score, polarity}

Passing a response category ID back as a filter fails: category.id=11 returns 400 ER0206 "category with ID '11' not found", while category.id=medtop:20001248 returns 299 articles. The IPTC code for a category is in its links.self (/news/category/iptc_mediatopics/medtop:20001248) — take it from there, or resolve names with /suggest/categories. Entities (entities[].id) and industries (industries[].id) are integers in both directions and round-trip cleanly; topics are dotted slugs in both.

The full field list is at https://docs.apitube.io/platform/news-api/response-structure

Rate limits and errors

  • Free: 10 requests per minute, 100 per day, and pagination stops after page 5 — page=6 returns 400 ER0173. Starter and above: 50 requests per minute. Test keys (api_test_) get their own 15 per minute.
  • Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (seconds).
  • 429 ER0203 — rate limit exceeded; back off until the reset. Repeated violations lead to a temporary ban (ER0204).
  • 401 ER0230 — the key has expired. 403 ER0601 / ER0602 / ER0603 — the key is restricted by IP, referrer, or endpoint scope.
  • Full list: https://docs.apitube.io/platform/news-api/http-response-codes

Check remaining quota at any time with GET /v1/balance.

Skills guardadas