1 · Quick start

# 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).

2 · Endpoints

method · pathdescription
GET /v1/index.jsonAll issuers with headline 5Y spread, status, and day-over-day delta
GET /v1/issuers.jsonSame issuer list, without index metadata
GET /v1/curves/{slug}/latest.jsonMost recent full curve for one issuer
GET /v1/curves/{slug}/{YYYY-MM-DD}.jsonImmutable curve for a specific publication date
GET /v1/history/{slug}.jsonDaily time series for one issuer
GET /v1/history/_index.jsonCoverage 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.

3 · Schemas

3.1 GET /v1/index.json

{
  "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": "…" } ]
}
fieldnotes
issuers[].as_ofData date (YYYY-MM-DD), not the publication timestamp
issuers[].statuspublished · published_volatile · withheld — see §4
issuers[].par_cds_5y_bp5Y par CDS in basis points; null when withheld
issuers[].delta_1d_bpChange vs previous published 5Y, bp; null on first day or withheld
issuers[].delta_daysCalendar days spanned by delta_1d_bp. Always read before calling a move "daily" — a Monday run reports 3
discount_sourceDiscount-curve provenance for the run

3.2 GET /v1/curves/{slug}/latest.json

{
  "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
}

3.3 GET /v1/history/{slug}.json

{
  "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.

4 · Gate semantics — read this before consuming

statusconditionpayload
publishedcurvature ≤ 20 bp/yr², no hard violations, no unsolved knotsfull spreads
published_volatilecurvature ≤ 60 bp/yr², or one unsolved knot short of the withhold rulefull spreads + volatility_flag
withheldcurvature > 60 bp/yr², any hard invariant broken, or the 5Y knot unsolved / ≥ half the knots unsolvedspreads 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.

5 · Errors and edge cases

situationresponse
unknown slug or unpublished date404 text/html — not JSON
withheld curve200 with status: "withheld" and no spreads
bare default Python-urllib/x.y User-Agent403 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.

6 · Caching

pathmutabilityguidance
/v1/curves/{slug}/{date}.jsonimmutablecache aggressively
/v1/curves/{slug}/latest.jsonchanges dailyrevalidate (If-None-Match) or short TTL
/v1/index.json · /v1/issuers.jsonchanges dailyrevalidate or short TTL
/v1/history/{slug}.jsonappended dailyshort TTL
/paid/v1/…as the matching /v1/ pathnever 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.

7 · Known limitations

  1. Not traded data — never present these as CDS quotes or marks.
  2. Fixed 40% recovery; not user-selectable in v1.
  3. History depth ≈ 1 year, window-bounded; extends only as time passes.
  4. Discounting-regime boundary inside history; regimes are not comparable level-for-level.
  5. Survivorship: backfilled rows are recomputed against today's bond universe.
  6. Coverage is uneven and changes — enumerate via the index/history endpoints.
  7. Bank issuers show elevated volatile/withheld rates pending a floating-rate-note universe filter.
  8. No intraday or streaming — daily, one snapshot per publication run (~22:00 ET).
  9. Publication lag: as_of is the prior trading day; data may be 1–2 days behind the session.
  10. Unsolved-knot placeholders (§3.2) — treat flagged tenors as missing data.
  11. The CDN can 403 unusual clients (§5) — send an explicit User-Agent.

8 · Pro — /paid/v1/

$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:

8.1 Authentication

curl -s -H "Authorization: Bearer <key>" \
     https://basisline.xyz/paid/v1/index.json

8.2 Billing

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.

8.3 Service posture

9 · Schema evolution

Additive only. Existing fields keep their name, type, and meaning; new fields may appear. Removing or repurposing a field is a version bump.