Skip to content
BondGov API
API Center

Simple access to BondGov issuer and disclosure data.

Use public endpoints for issuer discovery. Investor document access uses self-service API keys, exact CUSIP9 lookup, signed document links, and MuniPro as the cache and download intermediary.

Authenticate In One Minute

Start here: sign in to create a lookup key. The raw key appears once, with copy buttons, starter kit snippets, a browser playground, activity history, and a downloadable zip.

Use one header everywhere: Authorization: Bearer bgv1_...

Start with the auth check endpoint. It requires no CUSIP and returns your client, key prefix, scopes, quota state, reset time, and request id.

Then call doclist with a 9-character CUSIP. Use only the status, prepare, download, and text URLs returned by doclist.

# Bash / Linux
export BONDGOV_API_KEY="bgv1_your_key_here"

curl -s "https://bondgov.com/api/v1/investor-documents/auth/" \
  -H "Authorization: Bearer $BONDGOV_API_KEY"
# PowerShell
$env:BONDGOV_API_KEY="bgv1_your_key_here"

curl.exe -s "https://bondgov.com/api/v1/investor-documents/auth/" `
  -H "Authorization: Bearer $env:BONDGOV_API_KEY"
# Python
import os, requests

base = "https://bondgov.com/api/v1/investor-documents"
headers = {"Authorization": f"Bearer {os.environ['BONDGOV_API_KEY']}"}

auth = requests.get(f"{base}/auth/", headers=headers, timeout=30)
auth.raise_for_status()

docs = requests.get(
    f"{base}/doclist/",
    params={"cusip": "123456AB7", "scope": "all"},
    headers=headers,
    timeout=30,
)
docs.raise_for_status()
document = docs.json()["documents"][0]

prepare = requests.post(
    document["links"]["prepare"],
    params={"include": "download,text", "background": "true"},
    headers=headers,
    timeout=30,
)
prepare.raise_for_status()

status = requests.get(document["links"]["status"], headers=headers, timeout=30)
status.raise_for_status()
# JavaScript
const base = "https://bondgov.com/api/v1/investor-documents";
const headers = { Authorization: `Bearer ${process.env.BONDGOV_API_KEY}` };

const auth = await fetch(`${base}/auth/`, { headers });
if (!auth.ok) throw new Error(await auth.text());

const docs = await fetch(`${base}/doclist/?cusip=123456AB7&scope=all`, { headers });
if (!docs.ok) throw new Error(await docs.text());
const document = (await docs.json()).documents[0];

const prepare = await fetch(`${document.links.prepare}?include=download,text&background=true`, {
  method: "POST",
  headers
});
if (!prepare.ok) throw new Error(await prepare.text());

const status = await fetch(document.links.status, { headers });
if (!status.ok) throw new Error(await status.text());

Investor Document API

GET

/api/v1/investor-documents/doclist/

Find exact CUSIP, EMMA issuer, and accepted reporting-entity documents for one CUSIP9.

POST

/api/v1/investor-documents/{document_id}/prepare/

Warms a signed document into the cache and can queue extraction before the client downloads it.

GET

/api/v1/investor-documents/{document_id}/status/

Returns cache, extraction, and client-safe storage readiness without fetching upstream bytes.

GET

/api/v1/investor-documents/{document_id}/download/

Streams a PDF through the BondGov cache. Cache misses fetch upstream, persist, audit, and serve.

GET

/api/v1/investor-documents/{document_id}/text/

Returns extracted text, sections, tables, and image metadata. Pending extraction returns HTTP 202.

curl -s "https://bondgov.com/api/v1/investor-documents/doclist/?cusip=123456AB7&scope=all&limit=100" \
  -H "Authorization: Bearer $BONDGOV_API_KEY"
curl -X POST "https://bondgov.com/api/v1/investor-documents/{document_id}/prepare/?include=download,text&background=true" \
  -H "Authorization: Bearer $BONDGOV_API_KEY"
curl -I "https://bondgov.com/api/v1/investor-documents/{document_id}/download/" \
  -H "Authorization: Bearer $BONDGOV_API_KEY"
curl -L "https://bondgov.com/api/v1/investor-documents/{document_id}/download/" \
  -H "Authorization: Bearer $BONDGOV_API_KEY" \
  -o disclosure.pdf
curl -s "https://bondgov.com/api/v1/investor-documents/{document_id}/text/?include=sections,tables" \
  -H "Authorization: Bearer $BONDGOV_API_KEY"

Document Lifecycle

StepRequestClient Behavior
1GET /auth/Confirm the key, scopes, quota, and support request id.
2GET /doclist/?cusip=<CUSIP9>Read grouped and flattened document records. Save the returned signed document_id or links.
3POST /{document_id}/prepare/?include=download,text&background=trueQueue cache warming and extraction when a document is not ready yet. Use Retry-After before polling.
4GET /{document_id}/status/Check ready.download, ready.text, cache status, and extraction status without downloading the file.
5HEAD /{document_id}/download/Check PDF metadata and cache readiness before a bulk download job.
6GET /{document_id}/download/ or GET /{document_id}/text/Download the PDF or extracted content only through BondGov URLs returned by doclist.

Doclist supports safe filters: scope, limit, cursor, category, source, date_from, date_to, cache_status, and text_status. CUSIP6 searches are intentionally rejected in v1; send a full CUSIP9.

Scopes And Limits

ScopeAllowsNotes
documents:readDoclist lookupRequired for CUSIP discovery.
documents:downloadPDF downloadsSubject to request and byte quotas.
documents:textExtracted contentMay return 202 while extraction is queued.
documents:debugProvenance fieldsOptional operator/debug scope. Not needed for normal integrations.

Every investor document response includes X-BondGov-Request-ID for support correlation. Quota headers are returned when limits apply. Request responses include X-BondGov-RateLimit-Limit, X-BondGov-RateLimit-Remaining, and X-BondGov-RateLimit-Reset. Download responses can also include X-BondGov-Download-Limit, X-BondGov-Download-Used, and X-BondGov-Download-Remaining. Pending and temporary-failure responses include retry_after_seconds, Retry-After, and docs_url.

My API Keys Workspace

Fast Path

Create a lookup-only key with one button, then add PDF or text permissions only when the workflow needs them.

Playground

Run auth and doclist checks from the browser using one of your active keys, without pasting the raw token again.

Activity

Review a GitHub-style request frequency grid, recent API calls, request ids, errors, downloaded bytes, and requested-document history.

Defaults

Safety defaults are chosen for the user. Raw keys still appear once and are never stored.

Quota And Team Requests

Save structured requests for larger daily limits or shared team administration from the same account page.

Safe Rotation

Create a replacement key first, update your integration, confirm traffic, then revoke the old key manually.

Errors

StatusMeaningTypical Fix
202Accepted but not ready yet (cache warming or extraction in progress).Read retry_after_seconds and Retry-After, then poll status or retry after the delay.
400Invalid CUSIP, scope, limit, include value, cursor, or date filter.Use a normalized 9-character CUSIP and documented filters.
401Missing, invalid, expired, or revoked key.Create or test a key in My API Keys, then send it as Authorization: Bearer <key>.
403Key lacks the required scope.Create a replacement key with the preset that includes PDF download or extracted text access.
404CUSIP or signed document id was not found.Refresh doclist and retry with a returned link.
429Daily request or download quota exceeded.Use the quota headers and JSON reset time, then retry or save a higher-limit request from My API Keys.
500Unexpected server error.Include X-BondGov-Request-ID when contacting support.
502Upstream fetch or cache storage unavailable.Retry later; persistent failures should be escalated with the request id.
503Background preparation or extraction queue unavailable.Retry with Retry-After or download normally if the PDF is already ready.

Integration Best Practices

AreaGuidance
Always logSave the X-BondGov-Request-ID header from every response. Include it in support requests. Log quota headers (X-BondGov-RateLimit-Remaining, X-BondGov-Download-Remaining) to monitor usage before hitting limits.
Always storeKeep your bearer token only in a secret manager or environment variable. Never commit it to source control, logs, or shared documents.
Rotate safelyUse Create Replacement in My API Keys, update your environment variable, confirm traffic in recent requests, then revoke the old key manually.
Watch usageMy API Keys shows per-key usage, requested documents, request ids, errors, downloaded bytes, and a daily activity grid. Use it before escalating quota or authentication issues.
Never construct URLsUse only the links and document_id values returned by doclist. Do not build download, text, status, or prepare URLs from scratch. Document ids are signed and server-issued.
Handle 202 and Retry-AfterCache warming and text extraction are asynchronous. When you receive HTTP 202, read retry_after_seconds and poll /status/ before retrying. Do not spin in a tight loop.
Paginate large result setsUse limit, cursor, and doclist filters (category, source, date_from, date_to, cache_status, text_status) when syncing many documents. Follow the pagination.next_cursor field until pagination.has_more is false.
Use HEAD before bulk downloadsHEAD /{document_id}/download/ returns cache readiness and PDF metadata without streaming bytes. Use it to check before starting large download jobs.

Issuer Financials API

Up to ten fiscal years of income-statement totals extracted from an issuer's filed annual financial statements, with the source document, page, column and extractor confidence attached to every figure. Coverage is deliberately narrow today. Only statements an operator has reviewed and accepted are readable, and the accepted corpus is a small fraction of what has been extracted. Read coverage.accepted_statement_count on every response — it is the measured number for that issuer, and it may be zero.

GET

/api/v1/issuer-financials/{issuer_key}/income-statement/

Ten-year totals with provenance, restatements, continuity segments, and an explicit absent-year ledger. issuer_key is an issuer slug, profile:<id>, cusip6:<6> or cusip9:<9>.

GET

/api/v1/issuer-financials/{issuer_key}/statements/{statement_id}/

The full extracted line tree for one accepted statement, paged (default 100 lines, maximum 500).

curl -s "https://bondgov.com/api/v1/issuer-financials/state-of-california/income-statement/?years=10" \
  -H "Authorization: Bearer $BONDGOV_API_KEY"
ContractWhat it means for your integration
New scope: financials:readKeys issued before this endpoint launched do not gain it automatically. Existing keys receive HTTP 403 insufficient_scope naming the scope. Create a replacement key in My API Keys to pick it up.
REST and MCP are separate credentialsAn OAuth token issued for the MuniPro MCP server cannot authenticate this REST endpoint, and a REST API key is not an MCP credential. If you consume MuniPro over both, you need both.
A missing year is stated, never impliedEvery year inside window.span_from..window.span_to is either a point with a real value or a row in absent_years carrying a reason (no_afs_pointer, pointer_not_ingested, extracted_not_accepted, extraction_rejected, statement_kind_mismatch, accepted_no_totals_extracted, accepted_outside_requested_window). The reason is derived from database state and always agrees with the row’s own statement_status_counts. Gaps are never zero-filled, and a genuine zero is a real point.
Every metric key is always presentAll five keys appear in series[] on every response. Read status (series / single_year_only / not_extracted / suppressed_by_issuer) rather than inferring meaning from an absent key.
Truncation is declaredThe window is capped at ten years. When more accepted years exist, window.truncated is true and window.next_years_before is the value to pass as ?before= to walk back. window.page_size_capped tells you your ?years= was clamped.
Statement kinds are never silently mixedModified-accrual and full-accrual statements are not comparable. By default the modal statement kind wins and off-kind years are reported in absent_years as statement_kind_mismatch. Pass ?mix_statement_kinds=true to opt in; comparability.basis_consistent then reports false.
Restatements are recorded, never reconciledThe year’s own annual report is authoritative. Where a later report’s comparative column disagrees, the point carries restated, restated_value, restated_in_fiscal_year and the signed difference. The two figures are never averaged.
The restatement scan is corpus-wide, and says how widehas_restatements: false is only meaningful alongside restatement_scan, which reports scope, how many accepted statements it read, and complete. Detection is not limited to the page you asked for, so paging back through a walk cannot make a restatement disappear. If the scan could not widen it reports complete: false and raises restatement_scan_window_only rather than letting silence read as “no restatements”.
Is this the year’s own figure?Each point carries value_is_authoritative and an authority block. When a page reaches a year only through a later report’s comparative column, authority.reason is own_acfr_outside_window and it names the statement holding the year’s own number and that number’s value — so two pages of one walk returning different dollars for the same year explain themselves instead of silently disagreeing.
Provenance names the exact cellprovenance carries the line, column and evidence span the figure was READ from — recorded when the figure won its slot, not re-found afterwards by matching the number. Two lines holding the same dollars, or a current and comparative column that happen to be equal, cannot cross-cite. A figure we cannot tie to a single cell (a computed revenues total) reports provenance_resolved: false with a warning rather than a plausible-looking source.
Continuity breaks are structuralUse segments[] when charting. A polyline drawn across a break is a false statement about the issuer.
Numbers vs display stringsvalue is a lossless decimal string, value_float is a documented-lossy convenience, and value_display is for humans only. Values are full magnitude — scale is already applied, so never re-scale an “(in thousands)” statement.
These are extracted figuresNot audited statements, and not investment advice. versions stamps the extraction, taxonomy, triage and series versions; the external taxonomy mappings are still draft. Always confirm against the source filing linked from each point.
An issuer can suppress thisAn issuer who hides the income-statement module on their public page returns HTTP 200 with coverage.status = suppressed_by_issuer and no figures. That is a fact about the issuer, not an error.
Suppression is per-member on a consolidated issuerWhen an issuer rolls up several reporting-entity members, each member’s own suppression is honoured — a member that has hidden its income statement contributes no figure to any sibling’s series and no line tree on any sibling’s path. The consolidation block reports readable_member_profile_ids and suppressed_member_profile_ids so a short series is explicable rather than mysterious, and consolidation_members_suppressed_by_issuer is raised as a warning. Every point and every statement header names the member that FILED it in issuer_profile_id; statement detail adds filed_by_issuer.
Faults are 5xx, never empty dataAn issuer with no accepted statements is HTTP 200 with a zero coverage count. A backend fault is HTTP 500 financial_series_unavailable. Never treat one as the other.

Public Issuer API

Issuer search, CUSIP6 lookup, feeds, and embeddable profile widgets remain public read-only endpoints. These do not use investor document API keys.

BondGov A defensible public record for every investor who holds your bonds. Issuer investor-relations pages, compiled from public filings and managed by the issuers themselves.
© 2026 MuniPro, Inc. Data compiled from public sources. Not investment advice.

Coming Soon

We're putting the finishing touches on this feature. Check back shortly.