

This guide walks you through securely calling the Cisco Zero‑Day Highlights API (the endpoint that surfaced in the recent zero‑day disclosure). It covers everything from prerequisites to a production‑ready checklist, with ready‑to‑copy Python and JavaScript/TypeScript examples.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | How to get it |
|---|---|---|
| Cisco DevNet account | Required to obtain API credentials for the Zero‑Day Highlights service. | Sign up at https://developer.cisco.com/ and create a new API Key (or OAuth client) under My Apps. |
| API credentials | The endpoint expects either an X-API-Key header or a Bearer token obtained via OAuth 2.0 client‑credentials flow. | After creating the app, copy the Client ID, Client Secret, and (if using API‑key mode) the API Key. |
| Python 3.9+ (or newer) | For the Python example. | https://www.python.org/downloads/ |
| Node.js 18+ (or newer) | For the JavaScript/TypeScript example. | https://nodejs.org/ |
| Git (optional) | To clone the sample repo or manage your own code. | https://git-scm.com/ |
| IDE / editor | VS Code, PyCharm, WebStorm, etc. | Any modern editor works. |
| cURL (for quick testing) | Handy to verify the endpoint before writing code. | Pre‑installed on macOS/Linux; Windows users can install via Git‑Bash or Chocolatey. |
Note: The Cisco Zero‑Day Highlights API is a protected endpoint. Never commit raw credentials to source control. Use environment variables or a secret manager (see Step 4).
<a name="step-2-installation-and-setup"></a>
# 1️⃣ Create a virtual environment (recommended)
python -m venv venv
source venv/bin/activate # on Windows: venv\Scripts\activate
# 2️⃣ Install required packages
pip install --upgrade pip
pip install requests python-dotenv tenacity
requests – HTTP client.python-dotenv – loads .env files into os.environ.tenacity – simple retry/back‑off utility (used in the common patterns section).# 1️⃣ Initialise a new npm project (skip if you already have one)
npm init -y
# 2️⃣ Install dependencies
npm install axios dotenv typescript ts-node @types/node --save-dev
# 3️⃣ Create a basic tsconfig.json (if you don't have one)
npx tsc --init --rootDir src --outDir dist --esModuleInterop --resolveJsonModule --lib es6,dom
axios – Promise‑based HTTP client (works in Node and browsers).dotenv – loads environment variables.typescript + @types/node – for type‑safe TS development.<a name="step-3-basic-implementation"></a>
Below are complete, copy‑and‑paste ready snippets that:
https://api.cisco.com/v0/zeroday/highlights).Replace
YOUR_API_KEY,YOUR_CLIENT_ID, andYOUR_CLIENT_SECRETwith the values you obtained in Step 1.
The examples show both authentication styles; comment out the one you don’t need.
# file: cisco_zeroday.py
"""
Cisco Zero‑Day Highlights API client – Python version.
Features:
- Supports API‑key auth (X-API-Key) **or** OAuth 2.0 client‑credentials flow.
- Automatic token retrieval & caching (valid for 1 hour by default).
- Retry with exponential back‑off for transient errors (5xx, 429).
- Structured logging via the standard library logging module.
"""
import os
import json
import time
import logging
from typing import Optional, Dict
import requests
from dotenv import load_dotenv
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
# ----------------------------------------------------------------------
# Configuration & logging
# ----------------------------------------------------------------------
load_dotenv() # pulls variables from .env into os.environ
API_BASE_URL = os.getenv("CISCO_API_BASE", "https://api.cisco.com/v0")
ZERO_DAY_ENDPOINT = f"{API_BASE_URL}/zeroday/highlights"
# Choose auth method: set USE_API_KEY=true to use X-API-Key header,
# otherwise the script will fetch an OAuth token.
USE_API_KEY = os.getenv("USE_API_KEY", "false").lower() == "true"
API_KEY = os.getenv("CISCO_API_KEY")
CLIENT_ID = os.getenv("CISCO_CLIENT_ID")
CLIENT_SECRET = os.getenv("CISCO_CLIENT_SECRET")
TOKEN_URL = os.getenv(
"CISCO_TOKEN_URL", "https://cloudsso.cisco.com/as/token.oauth2"
) # Cisco's token endpoint (example)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)
log = logging.getLogger(__name__)
# ----------------------------------------------------------------------
# OAuth token handling (cached in-memory for simplicity)
# ----------------------------------------------------------------------
_cached_token: Optional[str] = None
_token_expires_at: float = 0.0 # epoch seconds
def _fetch_oauth_token() -> str:
"""Obtain a Bearer token using client‑credentials grant."""
global _cached_token, _token_expires_at
log.info("Requesting new OAuth token from %s", TOKEN_URL)
resp = requests.post(
TOKEN_URL,
data={"grant_type": "client_credentials"},
auth=(CLIENT_ID, CLIENT_SECRET),
timeout=10,
)
resp.raise_for_status()
data = resp.json()
_cached_token = data["access_token"]
# Cisco tokens usually have an `expires_in` field (seconds)
_token_expires_at = time.time() + int(data.get("expires_in", 3600)) - 30 # 30s safety margin
log.info("OAuth token acquired, expires in %s seconds", data.get("expires_in"))
return _cached_token
def get_auth_headers() -> Dict[str, str]:
"""Return the appropriate headers for the chosen auth method."""
if USE_API_KEY:
if not API_KEY:
raise RuntimeError("CISCO_API_KEY not set but USE_API_KEY=true")
return {"X-API-Key": API_KEY, "Accept": "application/json"}
# OAuth path
global _cached_token, _token_expires_at
if not _cached_token or time.time() >= _token_expires_at:
_fetch_oauth_token()
return {"Authorization": f"Bearer {_cached_token}", "Accept": "application/json"}
# ----------------------------------------------------------------------
# Core request logic with retry
# ----------------------------------------------------------------------
def _should_retry(exception: BaseException) -> bool:
"""Retry on connection errors, timeouts, or HTTP 5xx/429."""
if isinstance(exception, (requests.ConnectionError, requests.Timeout)):
return True
if isinstance(exception, requests.HTTPError):
return exception.response.status_code in {429, 500, 502, 503, 504}
return False
@retry(
reraise=True,
stop=stop_after_attempt(4),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((requests.ConnectionError, requests.Timeout, requests.HTTPError)),
)
def _make_request(url: str, headers: Dict[str, str]) -> requests.Response:
log.debug("GET %s with headers %s", url, {k: v for k, v in headers.items() if k.lower() != "authorization"})
resp = requests.get(url, headers=headers, timeout=15)
resp.raise_for_status() # will raise HTTPError for 4xx/5xx
return resp
def fetch_zero_day_highlights() -> Dict:
"""
Main entry point – returns the parsed JSON payload from the Cisco API.
Raises:
RuntimeError: if required credentials are missing.
requests.HTTPError: for non‑retryable HTTP errors (e.g., 401, 403, 404).
"""
headers = get_auth_headers()
try:
response = _make_request(ZERO_DAY_ENDPOINT, headers)
except requests.HTTPError as exc:
# Surface useful info for auth‑related problems
if exc.response.status_code == 401:
raise RuntimeError(
"Unauthorized (401). Check your API key or OAuth client credentials."
) from exc
if exc.response.status_code == 403:
raise RuntimeError(
"Forbidden (403). The token/API key may lack required scopes."
) from exc
raise # re‑raise other HTTP errors after retries exhausted
try:
data = response.json()
except json.JSONDecodeError as exc:
raise RuntimeError("Failed to decode JSON response") from exc
log.info("Successfully retrieved Zero‑Day Highlights (%d items)", len(data.get("items", [])))
return data
# ----------------------------------------------------------------------
# Simple CLI for manual testing
# ----------------------------------------------------------------------
if __name__ == "__main__":
try:
result = fetch_zero_day_highlights()
print(json.dumps(result, indent=2))
except Exception as err: # pragma: no cover – defensive
log.error("Error while calling Cisco API: %s", err)
exit(1)
How to run
# 1️⃣ Create a .env file (see Step 4)
# 2️⃣ Execute
python cisco_zeroday.py
// file: src/ciscoZeroDay.ts
/**
* Cisco Zero‑Day Highlights API client – TypeScript version.
*
* Demonstrates:
* • API‑key authentication (X-API-Key) **or** OAuth 2.0 client‑credentials flow.
* • Automatic token caching & refresh.
* • Retry logic with exponential back‑off (axios-retry).
* • Strong typing of the response payload.
*/
import axios, { AxiosInstance, AxiosError, AxiosResponse } from "axios";
import * as dotenv from "dotenv";
import { retry, exponentialBackOff } from "axios-retry";
import { config } from "dotenv";
config(); // loads .env into process.env
// ----------------------------------------------------------------------
// Environment variables
// ----------------------------------------------------------------------
const API_BASE = process.env.CISCO_API_BASE ?? "https://api.cisco.com/v0";
const ZERO_DAY_ENDPOINT = `${API_BASE}/zeroday/highlights`;
const USE_API_KEY = process.env.USE_API_KEY?.toLowerCase() === "true";
const API_KEY = process.env.CISCO_API_KEY;
const CLIENT_ID = process.env.CISCO_CLIENT_ID;
const CLIENT_SECRET = process.env.CISCO_CLIENT_SECRET;
const TOKEN_URL =
process.env.CISCO_TOKEN_URL ?? "https://cloudsso.cisco.com/as/token.oauth2";
if (!USE_API_KEY && (!CLIENT_ID || !CLIENT_SECRET)) {
throw new Error(
"OAuth mode selected but CISCO_CLIENT_ID or CISCO_CLIENT_SECRET missing"
);
}
if (USE_API_KEY && !API_KEY) {
throw new Error("API‑key mode selected but CISCO_API_KEY missing");
}
// ----------------------------------------------------------------------
// Axios instance with retry & logging
// ----------------------------------------------------------------------
const api: AxiosInstance = axios.create({
baseURL: API_BASE,
timeout: 15000, // 15 s
headers: { Accept: "application/json" },
});
// Retry on network errors, 429, 5xx
retry(api, {
retries: 3,
retryDelay: exponentialBackOff,
retryCondition: (error: AxiosError) => {
if (!error.response) return true; // network error
const status = error.response.status;
return status === 429 || status >= 500;
},
});
// Optional: log requests/responses in dev
if (process.env.NODE_ENV !== "production") {
api.interceptors.request.use((cfg) => {
console.debug(`[REQ] ${cfg.method?.toUpperCase()} ${cfg.url}`);
return cfg;
});
api.interceptors.response.use(
(res) => {
console.debug(`[RES] ${res.status} ${res.config.url}`);
return res;
},
(err) => {
console.error(`[ERR] ${err.message}`);
return Promise.reject(err);
}
);
}
// ----------------------------------------------------------------------
// OAuth token handling (simple in‑memory cache)
// ----------------------------------------------------------------------
let accessToken: string | null = null;
let tokenExpiresAt: number = 0; // epoch ms
async function fetchOAuthToken(): Promise<string> {
console.info("Fetching OAuth client‑credentials token...");
const { data } = await axios.post(
TOKEN_URL,
new URLSearchParams({ grant_type: "client_credentials" }),
{
auth: { username: CLIENT_ID!, password: CLIENT_SECRET! },
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}
);
accessToken = data.access_token;
// expires_in is in seconds; subtract 30 s for safety
tokenExpiresAt = Date.now() + (data.expires_in ?? 3600) * 1000 - 30_000;
console.info(`OAuth token acquired, expires in ${data.expires_in}s`);
return accessToken;
}
function getAuthHeaders(): Record<string, string> {
if (USE_API_KEY) {
return { "X-API-Key": API_KEY! };
}
// OAuth path – ensure we have a valid token
if (!accessToken || Date.now() >= tokenExpiresAt) {
// NOTE: fire‑and‑wait; in high‑throughput services you might want a lock or singleton.
// For this demo we just await.
// In a real service you'd use a mutex or a library like `async-lock`.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
fetchOAuthToken();
}
return { Authorization: `Bearer ${accessToken}` };
}
// ----------------------------------------------------------------------
// Response shape (adjust to the actual Cisco schema)
// ----------------------------------------------------------------------
interface ZeroDayItem {
id: string;
title: string;
severity: "low" | "medium" | "high" | "critical";
published: string; // ISO‑8601 date
cvssScore?: number;
advisoryUrl?: string;
}
interface ZeroDayResponse {
items: ZeroDayItem[];
totalCount: number;
// add any extra pagination fields if the API provides them
}
// ----------------------------------------------------------------------
// Core function
// ----------------------------------------------------------------------
export async function fetchZeroDayHighlights(): Promise<ZeroDayResponse> {
const headers = getAuthHeaders();
try {
const response: AxiosResponse<ZeroDayResponse> = await api.get(
"/zeroday/highlights",
{ headers }
);
return response.data;
} catch (err) {
const axiosErr = err as AxiosError;
if (axiosErr.response) {
const { status, data } = axiosErr.response;
if (status === 401) {
throw new Error(
`Unauthorized (401). Verify your API key or OAuth client credentials. ${JSON.stringify(
data
)}`
);
}
if (status === 403) {
throw new Error(
`Forbidden (403). The token/API key may lack required scopes. ${JSON.stringify(
data
)}`
);
}
}
// If we get here, the error is either network or a non‑retryable HTTP error
throw err;
}
}
// ----------------------------------------------------------------------
// Simple CLI demo (run with `ts-node src/ciscoZeroDay.ts`)
// ----------------------------------------------------------------------
if (require.main === module) {
(async () => {
try {
const result = await fetchZeroDayHighlights();
console.log(JSON.stringify(result, null, 2));
} catch (e) {
console.error("Failed to fetch Zero‑Day Highlights:", e);
process.exit(1);
}
})();
}
How to run
# 1️⃣ Create a .env file (see Step 4)
# 2️⃣ Compile & run with ts-node (no build step needed for demo)
npx ts-node src/ciscoZeroDay.ts
For a production build, run
npm run build(after adding a"build": "tsc"script) and execute the generated JavaScript indist/.
<a name="step-4-configuration"></a>
Create a .env file in the project root (never commit this file).
Below are the variables used by the snippets above.
# ======================
# Cisco API Endpoint
# ======================
# Base URL for all Cisco endpoints used in this demo.
CISCO_API_BASE=https://api.cisco.com/v0
# ======================
# Authentication Choice
# ======================
# Set to "true" to use a static API key (X-API-Key header).
# Set to "false" (or omit) to use OAuth 2.0 client‑credentials flow.
USE_API_KEY=false
# ----------------------
# API‑Key mode (if USE_API_KEY=true)
# ----------------------
CISCO_API_KEY=your_api_key_here
# ----------------------
# OAuth mode (if USE_API_KEY=false)
# ----------------------
CISCO_CLIENT_ID=your_client_id_here
CISCO_CLIENT_SECRET=your_client_secret_here
# Optional: override the token endpoint if your Cisco tenant uses a custom URL.
CISCO_TOKEN_URL=https://cloudsso.cisco.com/as/token.oauth2
# ======================
# Miscellaneous
# ======================
# Set to "development" or "production" to toggle verbose logging.
NODE_ENV=development
Loading the file
python-dotenv automatically loads it when you call load_dotenv().dotenv.config() (called at the top of the file) does the same.Security tip: In production, replace the
.envfile with a secret manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, etc.) and inject the values as environment variables at runtime.
<a name="step-5-common-patterns"></a>
Both examples use a retry library (tenacity for Python, axios-retry for Node) that:
429 Too Many Requests, and 5xx server errors.2^attempt * baseDelay seconds (with jitter if you enable it)._cached_token and _token_expires_at are refreshed only when the current token is missing or expired.accessToken / tokenExpiresAt work the same way; the fetchOAuthToken() function is called lazily before each request if needed.A small pure function (get_auth_headers() / getAuthHeaders()) returns the correct header map based on the chosen auth mode. This keeps request‑making code clean and makes unit‑testing trivial.
Raw HTTP errors are turned into domain‑specific exceptions (RuntimeError in Python, generic Error in TS) with helpful messages. This lets callers decide whether to surface the message to a user, log it, or trigger an alert.
logging module with a timestamped format.console.debug/info/error guarded by NODE_ENV !== "production" – replace with a proper logger (winston, pino) in real services.<a name="step-6-troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
401 Unauthorized (Python) / Unauthorized (401) (JS) | • Missing or incorrect CISCO_API_KEY or CISCO_CLIENT_ID/CISCO_CLIENT_SECRET.<br>• OAuth token expired and refresh failed (network issue or bad credentials). | 1. Verify the values in .env.<br>2. Ensure the API key has permission to read the Zero‑Day Highlights endpoint.<br>3. For OAuth, confirm the client‑credentials grant is allowed for the client (check in Cisco DevNet > My App > Authentication). |
403 Forbidden | • Token/API key valid but lacks required scope (e.g., security.advisory:read).<br>• IP‑based restrictions on the Cisco developer app. | 1. In DevNet, edit the app and add the needed scope(s).<br>2. If using IP allow‑list, add the outgoing IP of your host/service. |
429 Too Many Requests | • Exceeded rate limit (Cisco typically allows ≈ 5 req/sec per client). | 1. Implement client‑side throttling (e.g., bottleneck or p-limit).<br>2. Use the Retry-After header if present (the retry library already honors it for 429). |
500/502/503/504 | • Transient server issue on Cisco side. | Retry logic already handles these; if they persist, check Cisco status page (https://status.cisco.com/). |
ECONNREFUSED / ENOTFOUND | • Wrong base URL, missing internet connectivity, or DNS issue. | Confirm CISCO_API_BASE is correct and that you can reach it via curl -I https://api.cisco.com/v0. |
JSON parsing error (JSONDecodeError / SyntaxError) | • Received non‑JSON response (often an HTML error page). | Log the raw response text (response.text) to see what the server returned; usually indicates auth problem or wrong endpoint. |
| Token never refreshes (always 401) | • USE_API_KEY flag mis‑configured, causing the code to send an empty Bearer token. | Double‑check the boolean conversion (process.env.USE_API_KEY?.toLowerCase() === "true"). |
| Memory leak / token never cleared (long‑running service) | • Stale token held in memory after expiration. | In a daemon, add a periodic cleanup or use a library that manages token caching (e.g., axios-auth-refresh). |
Quick test with cURL
# API‑key mode
curl -H "X-API-Key: $CISCO_API_KEY" \
-H "Accept: application/json" \
https://api.cisco.com/v0/zeroday/highlights
# OAuth mode (fetch token first)
TOKEN=$(curl -s -u "$CISCO_CLIENT_ID:$CISCO_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
https://cloudsso.cisco.com/as/token.oauth2 | jq -r .access_token)
curl -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://api.cisco.com/v0/zeroday/highlights
If cURL works but your code doesn’t, compare the headers you’re sending.
<a name="step-7-production-checklist"></a>
| ✅ Item | Why it matters | How to implement |
|---|---|---|
| Never commit secrets | Prevent credential leakage. | Add .env to .gitignore. Use CI/CD secret injection (GitHub Actions secrets, GitLab CI variables, etc.). |
| Use HTTPS only | Protects credentials and data in transit. | Ensure CISCO_API_BASE starts with https://. Disable any fallback to HTTP. |
| Limit privilege scope | Reduces blast radius if a token is compromised. | Request only the needed scopes (e.g., security.advisory:read). |
| Implement rate‑limit handling | Avoid getting blocked by Cisco’s throttling. | Respect Retry-After header; add a token‑bucket or leaky‑bucket limiter (bottleneck, limax). |
| Centralised logging & monitoring | Enables fast detection of auth failures or abnormal traffic. | Send logs to a structured system (ELK, Splunk, Datadog). Alert on >5 × 401/403 per minute. |
| Health‑check endpoint | Lets orchestration (K8s, ECS) know the service is alive. | Expose /health that returns 200 when the API client can successfully fetch a small payload (or at least can obtain a token). |
| Circuit breaker | Prevents cascading failures when Cisco’s API is down. | Use opossum (Node) or pybreaker (Python) to open the circuit after N consecutive failures. |
| Automated token refresh | Guarantees long‑running workers never use expired tokens. | Wrap the HTTP client with a refresh interceptor (axios-auth-refresh, or custom middleware). |
| Version pinning | Guarantees reproducible builds. | Record exact versions in requirements.txt (pip freeze > requirements.txt) and package-lock.json. |
| Dependency scanning | Catches known vulnerabilities in your libraries. | Run safety check (Python) and npm audit or docker scan (Node) in CI. |
| Testing with mocks | Avoids hitting the real API during unit tests. | Use responses (Python) or nock/msw (JS) to simulate success, 401, 429, 500 responses. |
| Document the auth flow | Future developers need to know which method is active. | Keep a README.md that explains USE_API_KEY and how to obtain credentials. |
| Regular credential rotation | Limits exposure time of any leaked key. | Rotate API keys / client secrets every 90 days (or per your org policy). Update the CI/CD pipeline accordingly. |
| Audit logging (optional but recommended) | Provides an immutable record of who accessed what. | Log the request-id (if Cisco returns one) and the hash of the token/API key used (never the full value). |
You now have:
cisco_zeroday.py)src/ciscoZeroDay.ts)Copy the snippets into your own projects, adjust the endpoint URL if Cisco changes their API version, and you’ll be consuming the Cisco Zero‑Day Highlights API securely and reliably.
Happy coding, and stay safe out there! 🚀
Source: Dark Reading
Follow ICARAX for more AI insights and tutorials.
