How to Extract IPO Deal Terms From S-1 and F-1 Prospectuses

It is pricing day, you need use of proceeds, dilution and lock-up terms from a 400-page prospectus, and the section you need has no item number to anchor on.

In this article, you will learn:

  • Why S-1 and F-1 prospectuses defeat the parsers built for 10-K filings
  • The semantic section keys that normalize filer-chosen headings into queryable fields
  • How to pull use of proceeds, dilution, capitalization and underwriting in a few API calls
  • The F-1-specific sections that decide whether an ADR is investable
  • How to diff S-1 amendments to track changing deal terms across the registration period
  • And lastly, how to turn a 400-page prospectus into a structured deal-terms row — with a free S-1 filings API

An S-1 runs 300 to 500 pages, and somewhere inside it are the facts that actually drive an IPO decision: how much they are raising, what they will spend it on, pre- and post-money share count, how badly new investors are diluted, who is underwriting, when the lock-up expires, and for a foreign issuer, what exactly you are buying when you buy the ADS.

Almost everyone extracts these by hand. An analyst opens the prospectus, searches for “Use of Proceeds,” scrolls, reads the dilution table, and types figures into a spreadsheet — on pricing day, under time pressure, across a dozen live deals. It is one of the last genuinely manual jobs in equity research, and it stayed manual for a specific technical reason.

Why S-1 extraction defeats the tools that work on 10-K filings

Here is where your instincts will betray you. If you have already built section extraction for annual reports, your parser anchors on Item 1A, Item 7, Item 8 — anchors that are numbered, ordered and mandated. Prospectuses give you none of that.

A prospectus has no item numbers. Its sections are headings:

Prospectus Summary · Risk Factors · Use of Proceeds · Dividend Policy · Capitalization · Dilution · Management’s Discussion and Analysis · Business · Management · Executive Compensation · Principal Stockholders · Description of Capital Stock · Shares Eligible for Future Sale · Underwriting · Legal Matters

Which creates 4 compounding problems:

The wording varies by filer. “Principal Stockholders,” “Principal and Selling Stockholders,” and “Security Ownership of Certain Beneficial Owners and Management” are the same section. So are “Management’s Discussion and Analysis” and “Operating and Financial Review.” Your anchor list grows without end.

The order varies. Dilution sometimes precedes Capitalization. Industry Overview may be folded into Business or split out. Regulation may sit inside Business or stand alone.

Foreign issuers add sections that do not exist domestically. Description of American Depositary Shares. Enforceability of Civil Liabilities. Exchange Rate Information. PRC Regulations. A parser built for S-1 filings does not know these exist — and they are precisely the sections that matter most for an ADR.

The valuable content is in tables. Dilution is a table. Capitalization is a table. Text extraction that flattens tables into a stream of unlabeled numbers destroys exactly the information you came for.

What the free route costs you

Every S-1 is free on EDGAR, and downloading them was never the hard part. But I want to be honest with you about what the free path actually costs, because it is not zero. There are 3 bills, and they arrive in order.

The rate limit. The SEC caps automated access at 10 requests per second, per IP address, across all EDGAR domains, and enforces it with a 403 Forbidden and a roughly 10-minute IP block. Requests without a declared User-Agent naming a contact address are rejected outright. For a live IPO calendar with dozens of deals and hundreds of amendments, that ceiling is a genuine operational constraint.

Inline XBRL and raw HTML. Prospectus HTML is dense, table-heavy, and increasingly carries inline XBRL tags that shred naive text extraction — figures split across nested elements, hidden fact blocks dumped into the document body.

And then the parser. Because there are no item numbers, you are maintaining a heading-matching heuristic across every filer’s idiosyncratic formatting, forever. That is not a weekend project. It is a system, and it fails silently — you get a string back, it is just the wrong string.

This is why prospectus mining stayed manual while 10-K parsing got automated. The structure that made 10-K filings tractable simply is not there.

Semantic section keys for S-1 and F-1 filings

So prospectuses have no item numbers, filers title sections however they like, and the free route leaves you maintaining a heading-matcher forever. The way out is to let someone else do the normalization once and give you a stable key to query on.

Business Quant’s Section Extraction API covers S-1 and F-1 alongside the periodic forms — and because prospectuses have no numbering, the keys are semantic:

RiskFactors · UseOfProceeds · Dilution · Capitalization · Underwriting · MDA · Business · ProspectusSummary · ExecutiveCompensation · SecurityOwnership · DescriptionOfCapitalStock · SharesEligible · DividendPolicy · IndustryOverview · RelatedTransactions · Management · LegalProceedings · SelectedFinancialData · MaterialUSFederalIncomeTax · FinancialStatements

The normalization is the product. Whether the filer titled it “Principal Stockholders,” “Principal and Selling Shareholders,” or “Security Ownership,” it returns under SecurityOwnership. You stop maintaining a heading-variant list.

F-1 filings add the cross-border sections, and these are the ones that decide whether an ADR is investable:

KeyContainsWhy it matters
DescriptionOfADSADS-to-ordinary-share ratio, depositary fees, voting mechanicsThis is what you are actually buying. A 1:8 ratio and a 1:1 ratio are very different instruments at the same headline price.
RegulationRegulatory regime, including PRC regulationsVIE structures, CSRC filing requirements, data-security review, delisting exposure.
ServiceOfProcessEnforceability of civil liabilitiesIn plain terms: can you sue them, and where. For many issuers the honest answer is “not effectively.”
ExchangeRateInfoHistorical rates, translation methodologyYour returns are in 2 currencies whether you planned for it or not.
CorporateGovernanceBoard structure, home-country exemptionsForeign private issuers can opt out of most US governance standards. This section says which.
OurStrategy / OurStrengthsManagement’s own framingFrequently absent from domestic S-1 filings entirely.

Sections are pre-extracted at ingestion and served as clean HTML in sub-second lookups. Inline XBRL is already stripped. It is free.

Finding IPO filings by CIK

One thing to internalise before you write a line of code, because it will confuse you otherwise: a pre-IPO company has no ticker. Of course it does not — it has not listed yet. What it has is a CIK, and that is what you query on. Every code sample below does exactly that.

So the shape of an IPO pipeline is: take the CIK of any company in your deal pipeline, then run everything through the API from there — filing history, prospectus sections, amendment trail, exhibits. One identifier, 4 endpoints, and the 400 pages of structured content that used to be a week of manual work.

r = requests.get("https://data.businessquant.com/secfilings", params={
    "cik": 1234567,                            # pre-IPO: CIK, not ticker
    "formtype": "S-1,S-1/A,F-1,F-1/A,424B4",
    "from_date": "01-01-2025",                 # DD-MM-YYYY
    "api_key": API_KEY,
})
filings = r.json()["data"]

One call also tells you where a deal sits in its lifecycle. The Form Types API returns every form a CIK has ever filed. If the set contains 424B4 or 424B1, the deal has priced. If it contains S-1/A but no prospectus supplement, the company is still amending. That distinction matters because the sections you pull from a late-stage amendment carry near-final terms, while an initial S-1 is preliminary.

forms = set(requests.get(
    "https://data.businessquant.com/secfilings/formtypes",
    params={"cik": 1234567, "api_key": API_KEY},
).json()["data"]["formtypes"])

stage = ("priced"    if forms & {"424B4", "424B1"} else
         "amending"  if forms & {"S-1/A", "F-1/A"} else
         "filed"     if forms & {"S-1", "F-1"}     else "unknown")

That is your IPO pipeline dashboard, in one request per name.

Pulling the deal sections

You have the filings and you know the lifecycle stage. The next step is to pull the sections that carry the actual deal terms. The code below requests the core deal sections for a given accession and checks which ones the filer actually included before requesting them — because not every prospectus has every section, and the difference between “not present” and “present but empty” matters when you are building a structured dataset.

DEAL_SECTIONS = ["ProspectusSummary", "UseOfProceeds", "Capitalization", "Dilution",
                 "Underwriting", "SharesEligible", "RiskFactors", "MDA"]

FOREIGN_EXTRAS = ["DescriptionOfADS", "Regulation", "ServiceOfProcess", "ExchangeRateInfo"]

def section(accession, key):
    r = requests.get(
        f"https://data.businessquant.com/secfilings/{accession}/content",
        params={"item_key": key, "api_key": API_KEY},
    )
    return r.text        # clean HTML -- keep the markup for table-heavy sections

# Confirm what the filer actually included before requesting it
present = {row["item_key"] for row in requests.get(
    f"https://data.businessquant.com/secfilings/{accession}/items",
    params={"api_key": API_KEY},
).json()["data"]}

deal = {k: section(accession, k) for k in DEAL_SECTIONS if k in present}

Keep the HTML for the table-heavy sections. Dilution, Capitalization and Security Ownership are tables. Flattening them to plain text produces a stream of unlabeled numbers. Feed the markup to pandas.read_html() or straight to a language model, but do not destroy the structure on the way in.

Record omissions explicitly. A missing Dilution section on a follow-on offering is normal and meaningful. A missing Dilution section because your pipeline errored is a defect. If both come back as None, you can never tell them apart — which is why the /items call exists as the source of truth.

Turning sections into a deal terms row

Remember that sections are not the deliverable. The structured row is. And I want to underline something here, because it is the part people underestimate: LLM-assisted extraction against a pre-scoped, single-section input is a fundamentally different task from extraction against a 400-page document. Same model, same prompt, completely different reliability. That is exactly what section extraction buys you.

prompt = f"""Extract IPO deal terms as JSON:
{{"shares_offered": int|null, "price_range_low": float|null, "price_range_high": float|null,
  "greenshoe_shares": int|null, "dilution_per_share_usd": float|null,
  "lead_underwriters": [str], "lockup_days": int|null,
  "use_of_proceeds": [{{"purpose": str, "amount_usd": float|null}}]}}

Copy only figures explicitly stated. Never infer or calculate. Return JSON only.

{deal['Underwriting']}
{deal['Dilution']}
{deal['UseOfProceeds']}
"""

The never infer or calculate instruction is not decoration. Ask a model to compute gross proceeds from shares × price and it will happily do arithmetic on half-remembered numbers. Make it copy printed figures and null everything else, then derive the rest in code where you can see it.

Verify against a golden set of 20 deals you check by hand. Prospectus tables carry footnotes, “assuming no exercise of the underwriters’ option” caveats, and pre- and post-greenshoe columns that trip humans and models alike.

Diffing S-1 amendments to track changing terms

Extracting deal terms from a single filing is useful. Tracking how those terms change across amendments is where the real signal lives.

This is my favourite part of the pipeline, and it is the one that justifies building any of this. Almost nobody has it — not because it is a bad idea, but because until now it was simply too expensive to assemble.

An IPO does not file once. It files an S-1, then S-1/A, then S-1/A, sometimes 6 or 8 amendments over months. Terms move between them, and the movement is the signal:

  • A price range revised down before the roadshow ends means demand disappointed.
  • A share count cut means the deal was rightsized.
  • A new risk factor appearing in amendment 4 means something happened that counsel required them to disclose.
  • Softening lock-up terms mean insiders negotiated liquidity.
  • A newly added underwriter means the syndicate expanded.

Nobody diffs 400-page documents by hand. With section extraction, you diff one section at a time — pull the same key from the prior and latest accession, run a unified diff, and what comes back is exactly the lines that changed. The output is compact enough to scan by eye or feed to a model for summarization.

import difflib

before = section(prior_accession, "Underwriting")
after  = section(latest_accession, "Underwriting")

changes = [l for l in difflib.unified_diff(before.split("\n"), after.split("\n"))
           if l.startswith(("+", "-")) and not l.startswith(("+++", "---"))]

The output is an automatic term-sheet changelog for a deal in registration — per amendment, per section. Newly added risk factors are the highest-value output: a risk factor added 3 weeks before pricing was added because something forced it.

Building the lock-up calendar

Now let me point you at the section nobody reads and everybody should. SharesEligible — “Shares Eligible for Future Sale” — states how many shares are restricted, for how long, and precisely what happens when the restriction lifts. It is the supply overhang, written down, months in advance, by the company itself.

Lock-up expiry is one of the few genuinely predictable supply events in equities: a large, pre-announced, date-certain increase in float. Extract it at IPO, not the week before it hits, and cross-check against the Underwriting section, where the lock-up agreement itself is described and where price-trigger early releases show up.

Edge cases in IPO filings

The pipeline above handles the standard path. What follows are the assumptions it makes that will break on specific deal types or filing patterns — the kind of things you discover on the 3rd or 4th deal that does not fit.

Pre-IPO companies have no ticker. Query by cik. Ticker resolution only begins working once the company is listed and mapped.

Read the 424B4 as a document, not as sections. Final pricing lands in the 424B4, and you pull it through the Documents and Exhibits API — which returns the primary HTML with inline XBRL already stripped, so it drops straight into the same text pipeline as everything else.

Do not assume an amendment supports extraction — check. Read the items[] array on the filing record, or call /items on the accession. That is the source of truth, and it costs one call.

Underwriting agreements are exhibits, not sections. The Underwriting section is the prospectus’s description of the arrangement. The executed agreement is an EX-1 exhibit in the documents manifest — as is the legal opinion (EX-5) and the subsidiary list (EX-21).

Direct listings and SPACs do not fit the schema. A direct listing has no Use of Proceeds because there are no proceeds. A SPAC’s Dilution section means something entirely different. Flag deal type before extracting, or your database will average across instruments that are not comparable.

The short version

So why did prospectus mining stay manual for so long, when everything else got automated? Because prospectuses have no item numbers. The anchors that make 10-K parsing tractable simply do not exist here, and every issuer titles its sections a little differently. There was nothing to grab hold of.

Business Quant’s Section Extraction API covers S-1 and F-1 with semantic keys — UseOfProceeds, Dilution, Capitalization, Underwriting, SharesEligible, and for foreign issuers DescriptionOfADS, Regulation, ServiceOfProcess. Pre-extracted, sub-second, free. The SEC Filings API queries pre-IPO filers by CIK, the Form Types API tells you where a deal sits in its lifecycle, and the Documents and Exhibits API hands you the EX-1 underwriting agreement and the priced 424B4.

Which opens up 2 things that were simply not practical before. An IPO deal-terms database built from the primary documents rather than somebody else’s summary of them. And an amendment-by-amendment changelog showing exactly how a deal’s terms moved while it sat in registration — which, if you have ever tried to reconstruct that after the fact, you will know is worth the entire exercise on its own.

Frequently asked questions

Why can't a 10-K parser extract sections from an S-1 or F-1 prospectus?

A 10-K parser anchors on numbered items like Item 1A and Item 7. Prospectuses have no item numbers at all — their sections are headings that vary in wording and order by filer, such as "Principal Stockholders" versus "Security Ownership of Certain Beneficial Owners," so a numeric anchor never matches.

How do I query SEC filings for a company before its IPO?

Query by CIK rather than ticker. A pre-IPO company has no ticker symbol yet, and ticker resolution only starts working once the company is listed and mapped to an exchange.

Where do I find the ADS ratio and depositary fees for a foreign IPO?

In the DescriptionOfADS section of the F-1, a semantic key covering the American Depositary Share ratio, depositary fees and voting mechanics — the details that determine what you are actually buying relative to the ordinary share.

How can I track how IPO terms change across S-1 amendments?

Fetch the same section — Underwriting, Dilution, or Risk Factors — from the prior and latest accession and run a text diff between them. Because each section is pre-extracted and bounded, the diff surfaces exactly what changed, such as a revised price range or a newly added risk factor, without comparing 400-page documents by hand.

Is the Business Quant Section Extraction API free for S-1 and F-1 filings?

Yes. Both form types are covered with semantic section keys, pre-extracted and served as clean HTML in sub-second lookups, and every code sample on this page runs as written with a free API key.