# Latest 5Y par CDS for Oracle, with all tenors curl -s https://basisline.xyz/v1/curves/orcl/latest.json # Which issuers exist right now curl -s https://basisline.xyz/v1/index.json # One year of daily history curl -s https://basisline.xyz/v1/history/orcl.json
# Python 3.8+ standard library only — no dependencies import json, urllib.request # ALWAYS set a User-Agent: the CDN returns 403 to the bare # `Python-urllib/x.y` default. Any explicit value works. UA = "MyApp/1.0 ([email protected])" def get(path): req = urllib.request.Request(f"https://basisline.xyz/v1/{path}", headers={"User-Agent": UA, "Accept": "application/json"}) with urllib.request.urlopen(req, timeout=30) as r: return json.load(r) curve = get("curves/orcl/latest.json") print(curve["issuer"], curve["as_of"], curve["par_cds_bp"]["5.0"], "bp") print("status:", curve["status"], "| gate_ok:", curve["gate_ok"])
No API key is required on the free tier. The paid tier authenticates with
Authorization: Bearer <key> against the /paid/v1/ prefix (§8).
| method · path | description |
|---|---|
| GET /v1/index.json | All issuers with headline 5Y spread, status, and day-over-day delta |
| GET /v1/issuers.json | Same issuer list, without index metadata |
| GET /v1/curves/{slug}/latest.json | Most recent full curve for one issuer |
| GET /v1/curves/{slug}/{YYYY-MM-DD}.json | Immutable curve for a specific publication date |
| GET /v1/history/{slug}.json | Daily time series for one issuer |
| GET /v1/history/_index.json | Coverage summary for every issuer's history |
All responses are application/json; charset=utf-8, gzip-compressed when the client sends
Accept-Encoding: gzip. Issuer slugs are lowercase short codes (orcl,
msft, jpm, …) — always enumerate via /v1/index.json, never hard-code.
Every path above also exists under /paid/v1/ with an identical schema plus paid-only enrichment
(§8); the /v1/ paths remain unauthenticated and unchanged.
{
"product": "Basisline",
"labeling": "model-implied, not traded",
"disclaimer": "…",
"discount_source": "SR3 futures strip to 2.21y via Yahoo/CME",
"updated_at": "2026-10-03T02:27:35.227469+00:00",
"issuers": [ { "slug": "orcl", "issuer": "ORACLE CORP", "as_of": "2026-10-02",
"status": "published", "par_cds_5y_bp": 216.7,
"delta_1d_bp": 0.1, "delta_days": 1, "discount_source": "…" } ]
}
| field | notes |
|---|---|
| issuers[].as_of | Data date (YYYY-MM-DD), not the publication timestamp |
| issuers[].status | published · published_volatile · withheld — see §4 |
| issuers[].par_cds_5y_bp | 5Y par CDS in basis points; null when withheld |
| issuers[].delta_1d_bp | Change vs previous published 5Y, bp; null on first day or withheld |
| issuers[].delta_days | Calendar days spanned by delta_1d_bp. Always read before calling a move "daily" — a Monday run reports 3 |
| discount_source | Discount-curve provenance for the run |
{
"slug": "orcl", "issuer": "ORACLE CORP", "as_of": "2026-10-02",
"recovery_assumption": 0.4, "gate_ok": true,
"n_bonds_priced": 52, "n_prints_day": 2227,
"negative_z_knots": [], "unreachable_low_knots": [], "unsolved_knots": [],
"status": "published",
"z_at_knots_bp": { "1.0": 109.4, "2.0": 120.7, "3.0": 151.8, "5.0": 214.0, "7.0": 249.0, "10.0": 266.5 },
"hazards_bp": { "1.0": 181.3, "2.0": 220.0, "3.0": 366.4, "5.0": 546.5, "7.0": 616.2, "10.0": 554.9 },
"par_cds_bp": { "1.0": 111.9, "2.0": 128.3, "3.0": 160.5, "5.0": 216.5, "7.0": 247.5, "10.0": 263.8 },
"par_cds_5y_prev_bp": 215.6, "delta_1d_bp": 0.9,
"delta_basis_date": "2026-10-01", "delta_days": 1
}
"5.0", not "5". Parse as floats.par_cds_bp at that tenor is a placeholder, not a fit; treat it as missing data.
This is the field to consume; the individual flag lists are for debugging.delta_days and delta_basis_date.status is withheld, z_at_knots_bp,
hazards_bp and par_cds_bp are absent and a gate_violations array
explains why (§4).latest.json for
"now"; use the dated file as the immutable record.{
"slug": "orcl", "issuer": "ORACLE CORP",
"start": "2025-09-29", "end": "2026-10-01", "days": 252,
"series": [ { "date": "2026-10-01", "n_bonds_priced": 55, "n_prints_day": 55,
"sofr_pct": 3.9, "curve_regime": "…",
"z_at_knots_bp": { … }, "par_cds_bp": { … }, "par_cds_5y_bp": 216.5 } ],
"regime_note": "…"
}
Ascending by date; sofr_pct is in percent; curve_regime marks the
discounting regime of each row. Read regime_note — it explains the regime boundary inside
the series, and the regimes are not comparable level-for-level. History rows carry no gate status: a row is a
recomputed historical curve, not an archived published payload. Use /v1/history/_index.json (a
bare JSON array of {slug, issuer, days, start, end}) to discover coverage; depth differs by
issuer and is bounded by the rolling one-year print window.
| status | condition | payload |
|---|---|---|
| published | curvature ≤ 20 bp/yr², no hard violations, no unsolved knots | full spreads |
| published_volatile | curvature ≤ 60 bp/yr², or one unsolved knot short of the withhold rule | full spreads + volatility_flag |
| withheld | curvature > 60 bp/yr², any hard invariant broken, or the 5Y knot unsolved / ≥ half the knots unsolved | spreads stripped; gate_violations instead |
In /v1/index.json a withheld issuer shows par_cds_5y_bp: null. A withheld curve
payload still carries par_cds_5y_prev_bp and the delta fields (with delta_1d_bp: null).
Handle all three states explicitly in your client; do not assume spreads are present. Details:
methodology §7.
| situation | response |
|---|---|
| unknown slug or unpublished date | 404 text/html — not JSON |
| withheld curve | 200 with status: "withheld" and no spreads |
bare default Python-urllib/x.y User-Agent | 403 from the CDN before reaching the API |
| missing / malformed / revoked key on /paid/v1/ | 401 application/json (§8) |
| over-quota key on /paid/v1/ (10,000 calls per calendar month) | 429 application/json — every authenticated call counts; the counter resets with the UTC month (§8) |
There is no JSON error envelope on the free plane — a 404 body is HTML. Always check the HTTP status code
before parsing. Practical rule on the 403: send any explicit User-Agent identifying your
client; only Python's bare urllib default is affected — requests, httpx,
curl and other mainstream clients send their own and are unaffected.
| path | mutability | guidance |
|---|---|---|
| /v1/curves/{slug}/{date}.json | immutable | cache aggressively |
| /v1/curves/{slug}/latest.json | changes daily | revalidate (If-None-Match) or short TTL |
| /v1/index.json · /v1/issuers.json | changes daily | revalidate or short TTL |
| /v1/history/{slug}.json | appended daily | short TTL |
| /paid/v1/… | as the matching /v1/ path | never cache publicly (§8) |
All of /v1/ sends Cache-Control: public, immutable with ETag and
Last-Modified — treat those as authoritative only for date-keyed files. For reproducible work,
pin the dated file. The paid plane sends Cache-Control: private, no-store and
Vary: Authorization on every response, errors included; if you proxy Basisline, preserve
Vary and do not cache /paid/ responses in any shared layer.
as_of is the prior trading day; data may be 1–2 days behind the session.$99/month, checkout on this site, API key issued instantly. Every /paid/v1/ path mirrors
the corresponding /v1/ path one-for-one and returns the same schema plus paid-only
enrichment:
recovery_grid — upfront points (bp of notional, negative = you receive) per recovery
20/30/40/50% × running coupon 100/500 bp × tenor 1–10Y, recomputed on the same fitted hazard curve.
Answers what the protection is worth under a different recovery — the question the fixed 40% assumption
leaves open. Present only when the issuer clears its gate.gate_checks — the gate limits and per-flag lists (never withheld spreads).plane: "paid" marker on every paid payload; paid/v1/history/ mirrors the free history.curl -s -H "Authorization: Bearer <key>" \
https://basisline.xyz/paid/v1/index.json
bl_-prefixed; the header is Authorization: Bearer <key>
(scheme case-insensitive).{ "error": "a valid API key is required",
"header": "Authorization: Bearer <key>",
"docs": "https://basisline.xyz/api.html" }
Treat 401 as "key missing, rotated, or expired" — do not retry unchanged.Stripe Checkout, monthly subscription, cancel any time. Stripe is the seller of record: Stripe
calculates, collects and remits VAT/sales tax and issues the tax invoices, so the listed price is what you
pay. The flow: POST /checkout returns a Stripe-hosted URL; after payment your browser lands on
the claim page, which exchanges the session for your key — the only place the key is ever shown.
POST /portal with your key returns a Stripe-hosted Billing Portal URL (change card, download
invoices, cancel). Cancelling takes effect at the end of the period you have already paid for — you
keep access until then; the key is revoked when that period ends. A failed payment does not revoke the key
either: the subscription goes past_due, Stripe retries for a couple of weeks, and your key keeps
working throughout. Two properties matter operationally: the portal URL is a credential (never cached, expires
~5 minutes unused), and the portal is not a key-recovery route — Basisline cannot show you a key it
only holds as a hash.
/paid/v1/ denies
rather than serving ungated; the free plane is unaffected.Additive only. Existing fields keep their name, type, and meaning; new fields may appear. Removing or repurposing a field is a version bump.