eol.network API
v1 · REST · JSON · one tier, currently free
TL;DR
- Register, verify your email.
- Mint an API key from /account. You see the plaintext once. Keep it.
- Send it as
Authorization: Bearer <key>orX-API-Key: <key>. - Hit the endpoints below. JSON in, JSON out. 60 req/min, 5000/day.
Authentication
Every /api/v1/* call needs a key. No key → 401 missing_key. Revoked or bogus key → 401 invalid_key. Unverified email → 403 email_unverified.
curl -H "Authorization: Bearer eol_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
https://eol.network/api/v1/vendors
Keys never expire. Rotate them yourself if one leaks — revoke the old key in /account and mint a new one. I don't do automatic rotation, you're adults.
Rate limits
- Free tier: 60 req/min, 5000/day (per key).
- Over the minute bucket:
429 rate_limit_minute,Retry-After: 60. - Over the daily quota:
429 quota_exhausted, resets at 00:00 UTC. - Every successful response includes
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset.
If you actually need more, tell me what you're building. Paid tiers may happen; probably not.
Response shape
All success responses are { "data": ..., "meta": { ... } }. Errors are { "error": { "code": "...", "message": "..." } }. The code field is stable; branch on it, not on the message text.
Endpoints
GET/api/v1/vendors
All vendors, with product count + last generated timestamp.
GET /api/v1/vendors
{
"data": [
{ "slug": "juniper", "name": "Juniper", "count": 3519, "generated_at": "2026-04-14T02:37:16Z", ... },
{ "slug": "arista", "name": "Arista", "count": 736, ... },
...
]
}
GET/api/v1/vendors/{vendor}
Vendor summary: series list, product-type counts, lifecycle-status breakdown.
GET/api/v1/vendors/{vendor}/products
Paginated product list. Default per_page=100, max 500.
| Query | Values | Notes |
|---|---|---|
| type | chassis, optic, appliance, line-card, … | exact match on product_type |
| series | e.g. MX, EX, 7280r3 | case-insensitive, matches any of applicable_series |
| status | active, announced, eos, eol | lifecycle_status derived from dates |
| page | int ≥ 1 | 1-indexed |
| per_page | 1 – 500 | default 100 |
GET /api/v1/vendors/juniper/products?type=chassis&per_page=3
{
"data": [
{
"product_id": "MX480",
"product_name": "MX480 Universal Routing Platform",
"product_type": "chassis",
"applicable_series": ["MX"],
"lifecycle_status": "announced",
"end_of_sale_date": "2028-12-31",
"last_date_of_support": "2033-12-31",
...
},
...
],
"meta": { "total": 147, "page": 1, "per_page": 3 }
}
GET/api/v1/vendors/{vendor}/products/{product_id}
Single product. product_id is URL-path-encoded and may contain slashes (route captures the rest of the path).
Returns 404 product not found for unknown IDs.
GET/api/v1/search?q=...
SKU / model / name fuzzy search across all vendors. Minimum 2-char query. Optional vendor= to scope.
GET /api/v1/search?q=MX480
{
"data": [
{ "vendor_slug": "juniper", "product_id": "MX480", "product_name": "...", "end_of_sale_date": "..." },
...
],
"meta": { "total": 4 }
}
GET/api/v1/lifecycle/upcoming
What's about to go bad inside a window. Useful for cron-driven asset-refresh reminders.
| Query | Values | Default |
|---|---|---|
| kind | any, eos, eol, eosw | any |
| within | 7d, 30d, 90d, 180d, 365d | 90d |
| vendor | slug | (all vendors) |
GET /api/v1/lifecycle/upcoming?kind=eos&within=90d&vendor=arista
{
"data": [
{
"vendor_slug": "arista",
"vendor_name": "Arista",
"product_id": "DCS-7280QR-C36",
"product_name": "...",
"product_type": "chassis",
"milestone": "eos",
"date": "2026-05-01"
},
...
],
"meta": { "total": 17, "within": "90d", "kind": "eos" }
}
GET/api/v1/events?year=&month=
All lifecycle events in a calendar month. Same data that powers /calendar.
Fields you'll see on a product
| product_id | Vendor SKU / part number. Stable URL key. |
| product_name | Short human-recognizable model / display name. |
| applicable_series | Array of series slugs for grouping (MX, 7280r3, PA-5200…). |
| product_type | chassis, line-card, optic, appliance, fan, power-supply, software-license, … |
| category | Derived from type (never stored independently). |
| end_of_sale_date | EOS. Last day the vendor will take a PO for the SKU. |
| end_of_software_support_date | EOSW. Last software / security updates. |
| last_date_of_support | EOL. Last day of any hardware support contract. |
| replacement_product | Vendor-suggested successor SKU, if published. |
| lifecycle_status | active | announced | eos | eol — derived from the above against today. |
| extra | Vendor-specific extras (notes, disclaimers, series-level metadata). |
What this API won't give you
- Source URLs to the vendor publications. They're stripped at the data layer. Go read the vendor page yourself — the site navigates to it.
- Raw conflict records (when a SKU has multiple source entries, the database reconciles to earliest-sale / latest-support; you get the reconciled record only).
- Pricing. Nobody publishes this publicly, I don't know it either.
- Contract-entitled data (Cisco, Fortinet, Nokia, Ciena, Adtran). If it needs a login to read, it's not here.
Errors you'll see
| HTTP | error.code | Meaning |
|---|---|---|
| 401 | missing_key | No Authorization header or X-API-Key. |
| 401 | invalid_key | Key not recognized or revoked. |
| 403 | email_unverified | User hasn't clicked the verify link yet. |
| 403 | user_inactive | Account disabled. |
| 404 | — | Vendor or product not found. |
| 400 | bad_within / bad_kind | Parameter out of the allowed set. |
| 429 | rate_limit_minute | Back off for 60s. |
| 429 | quota_exhausted | Daily quota gone. Resets 00:00 UTC. |
| 503 | db_unavailable | Database pool not ready. Retry. |
Etiquette
- Cache on your side. This data changes weekly at best; most records, yearly.
- Don't scrape the site HTML — the API exists for a reason and the WAF will notice.
- Set a sensible User-Agent with your project name + contact URL.
- If something is wrong or missing, tell me. Data-quality fixes land faster than feature work.