News API

Three GET endpoints. One bearer token. Cyber-security stories from 28 outlets, rewritten into plain-language briefs and tagged by topic, as JSON.

Authentication

Send your key as a bearer token:

Authorization: Bearer cisoai_...

X-API-Key: cisoai_... is accepted as an alternative, because clients reach for it. Bearer is the documented form.

Keys belong on your server, never in a browser

Anything in client-side JavaScript is readable by every visitor. A key in a web page is a key anyone can take, and the usage bills to you. Call this API from your backend and serve the result to your own front end — there is a worked example of exactly that below.

Endpoints

EndpointReturns
GET /api/newsThe newest briefs in full, up to 200. Details
GET /api/archiveSearch every brief ever published, by keyword, tag, source and date. A light listing without bodies. Details
GET /api/news/{slug}One brief in full, however old. Details

All three are on https://cisoai.au, take the same key and count against the same rate limit. There is no SDK and no OAuth flow.

To run a request from your browser with your own key, use the interactive reference. It is generated from the OpenAPI file, which you can also import into Postman or a code generator.

Latest briefs

GET https://cisoai.au/api/news
ParameterTypeDefaultDescription
limitinteger20 How many stories to return. Clamped to your plan's ceiling — asking for more is not an error, you simply receive the ceiling.
categorystring— Exact match on a story's category. See the list below.
sourcestring— Exact match on the publishing outlet, e.g. Dark Reading.
distinct1— Collapse duplicate coverage so one event does not fill the page with nine versions of itself. Useful for a feed; leave it off for analysis.

Categories

Threat Intelligence · Security News · Cybersecurity Research · Government Advisory · Industry News

Sources

The Hacker News, Infosecurity Magazine, CrowdStrike, Cisco Talos, Unit 42, Project Zero, Krebs on Security, Dark Reading, CISA, ASD's ACSC, and a financial wire whose per-story publisher is used as the source name. The live list is in every response under sources, as name and category pairs, so you never have to hard-code it.

Response

{
  "articles": [ /* see fields below */ ],
  "count": 20,
  "plan": "pro",
  "limit": 20,
  "fullBody": true,
  "sources": [ { "name": "Dark Reading", "category": "Threat Intelligence" } ]
}

sources lists every outlet the feed reads, whatever filters the request used.

Article fields

FieldTypeDescription
headlinestringRewritten headline.
summarystringOne sentence.
bodystringThe full rewritten article, paragraphs separated by \n\n.
keyTakeawaystringOne sentence of practical advice.
tagsstring[]Topic tags assigned at ingest.
categorystringOne of the categories above.
sourcestringPublishing outlet.
originalUrlstringThe original article. Link to this when you republish.
originalTitlestringThe headline as published by the source.
publishedAtISO 8601When the source published it. Never in the future.
createdAtISO 8601When we ingested it.
slugstringStable identifier. cisoai.au/brief/<slug> is the human-readable page.
idstringInternal identifier. Prefer slug.
viastringPresent only on stories that reached us through a wire rather than from the outlet directly. Names the wire, e.g. Financial Press; source still names the publisher.
primarySourcesobject[]Present only where the story cites an official statement, such as a government advisory or an agency post. Each entry has a url and a label.
imageUrlstringSource image where one exists, otherwise empty. Roughly two thirds of stories have one.

Fields are additive. Nothing already published gets renamed or removed without notice, so parsing defensively for new keys is safe and parsing strictly is not.

Archive search

GET https://cisoai.au/api/archive

/api/news stops at the newest 200 stories. The archive goes back to the first brief, in May 2025, and is where you search. It returns a light listing; fetch the full text of any result from /api/news/{slug}.

ParameterTypeDefaultDescription
qstring  Keywords, matched against the headline and tags. Every word must match, so microsoft ransomware narrows the search. Case does not matter.
tagstring  Exact match on one tag, written either as it appears in tags (AI security) or as a topic slug (ai-security).
sourcestring  Exact match on the publishing outlet.
from, todate  Publication date range. YYYY-MM-DD covers the whole day at both ends, in UTC; a full ISO 8601 timestamp is taken as given.
limitinteger20 Page size, up to 100. Clamped rather than rejected.
cursorstring  The next value from the previous page, unchanged. Keep the other parameters the same while you page.
{
  "articles": [
    {
      "slug": "critical-sonicwall-sma-1000-flaw",
      "headline": "Critical SonicWall SMA 1000 Flaw Is Being Actively Exploited",
      "source": "Dark Reading",
      "tags": ["SonicWall", "vulnerability"],
      "publishedAt": "2026-10-12T21:42:42.000Z",
      "url": "https://cisoai.au/brief/critical-sonicwall-sma-1000-flaw"
    }
  ],
  "count": 1,
  "total": 105,
  "limit": 20,
  "next": "MjAyNi0xMC0xMlQyMTo0Mjo0Mi4wMDBafGNyaXRpY2Fs...",
  "plan": "pro"
}

Results are newest first. total is the number of briefs that match, across all pages. next is null on the last page; to read everything, keep passing it back as cursor until it is. A cursor marks a place in the results rather than a page number, so stories published while you are paging never cause one to repeat.

The archive is rebuilt after each ingest run, so a new story can appear in /api/news before it appears here. Search covers headlines and tags, not the full text of the briefs.

One brief by slug

GET https://cisoai.au/api/news/{slug}

Returns one brief with every field in the article table above, including body. It works for any brief in the archive, not only the newest 200, so it is also how you fetch a story again from a slug you stored.

{
  "article": { /* the same fields as an entry in /api/news */ },
  "plan": "pro",
  "fullBody": true
}

A slug that does not exist answers 404.

Access

Key requiredYes, on every request
Stories per requestUp to 200 from /api/news, 100 per page from /api/archive
Archive search and lookup by slugIncluded
body (full article text)Included
FiltersYes
Rate limit60 requests a minute
Endpoints/api/news, /api/archive, /api/news/{slug}

There is no keyless tier. A request without a key is answered 401, here and on every other hostname the feed is served from. To see the briefs before you buy, read them on cisoai.au: the API returns the same stories.

The rate limit is listed because it should not be a surprise after you have paid. The corpus only changes when ingest runs, every three hours, so 60 requests a minute is far above what reading it needs. If you are close to it, cache the response rather than asking us for a higher ceiling.

Pricing and keys →

Errors

StatusMeaningWhat to do
400Archive only: from, to or cursor could not be read. The body says which. Fix the parameter. A bad value is refused rather than ignored, so a typo never returns the wrong stories.
401No key sent, or the key is not recognised. Check the Authorization header. Retrying will not help.
403The key is known but not active — usually past_due after a failed payment. The body names the state. Update your card. Your key resumes working; you never need a new one.
404Lookup by slug only: no brief has that slug. Check the slug. Retrying will not help.
405Method other than GET.Use GET.
429Over 60 requests in a minute on this key. Wait the number of seconds in Retry-After, then continue. Your key is fine. If you are hitting this, cache your responses — the corpus only changes every three hours.
502The feed is briefly unavailable upstream. Retry with backoff. Your last good response is still valid — the corpus only changes every three hours.
503We could not verify your key. This is our problem, not a bad key.Retry with backoff.

Errors return JSON with an error field. Never treat a 5xx as an authentication failure — the two are deliberately kept distinct so you are not chasing a key problem during an outage.

Examples

curl

curl "https://cisoai.au/api/news?limit=50&category=Threat%20Intelligence" \
  -H "Authorization: Bearer $CISOAI_API_KEY"

Node

// Server-side. Never ship the key to a browser.
const response = await fetch(
  "https://cisoai.au/api/news?limit=200",
  { headers: { Authorization: `Bearer ${process.env.CISOAI_API_KEY}` } }
);
if (!response.ok) throw new Error(`news api ${response.status}`);
const { articles } = await response.json();

Python

import os, requests

r = requests.get(
    "https://cisoai.au/api/news",
    params={"limit": 200, "distinct": 1},
    headers={"Authorization": f"Bearer {os.environ['CISOAI_API_KEY']}"},
    timeout=30,
)
r.raise_for_status()
articles = r.json()["articles"]

Reading the whole archive

import os, requests

headers = {"Authorization": f"Bearer {os.environ['CISOAI_API_KEY']}"}
params = {"q": "ransomware", "from": "2026-01-01", "limit": 100}
found = []
while True:
    page = requests.get("https://cisoai.au/api/archive", params=params, headers=headers, timeout=30).json()
    found += page["articles"]
    if not page["next"]:
        break
    params["cursor"] = page["next"]

# The full text of one result
brief = requests.get(f"https://cisoai.au/api/news/{found[0]['slug']}", headers=headers, timeout=30).json()["article"]

Serving it to your own front end

The pattern to copy: a small server-side route holds the key and your page calls that route same-origin. This is how bugxhunter.com renders its feed — a Cloudflare Pages Function that proxies, with the key in a secret and never in the markup.

// functions/api/news.js — your origin, your cache, your key stays server-side
export async function onRequest({ env }) {
  const upstream = await fetch("https://cisoai.au/api/news?limit=24", {
    headers: { Authorization: `Bearer ${env.CISOAI_API_KEY}` },
  });
  if (!upstream.ok) return new Response('{"articles":[]}', {
    headers: { "Content-Type": "application/json" },
  });
  return new Response(await upstream.text(), {
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "public, max-age=300",
    },
  });
}

Note the empty-list fallback rather than an error: a news section is worth degrading, not worth breaking a page over.

Limits and freshness

Managing your key

Attribution and terms

Briefs are rewritten summaries of publicly available reporting, not reproductions of it. Every record names its source and links the originalUrl.

Questions: [email protected]
Get a key  ·  Home  ·  Coverage analysis