Claim trees
PatentRef's addition to the grammar: the claim tree of one granted patent, read from its claims as published. Every part of a claim carries start and end offsets into the claim text, and flags note what the parser saw in the text and the data. A flag or a category describes the text as printed; nothing here is a legal conclusion.
- Route
GET /api/v1/structure/claims/{patent_id}/ (the trailing slash is optional)
- Key
X-Api-Key, as every data path; it counts toward the monthly allowance
- Built
- when it is asked for, by a deterministic parser (regular expressions and small state machines, no language model)
- Versions
structure_version, data_version (X-Data-Version) and text_data_version (X-Text-Data-Version)
- Machine-readable
- the operation in openapi.json (operationId
StructureClaims, schema ClaimTree)
Example
curl -s -H "X-Api-Key: $KEY" https://patentref.io/api/v1/structure/claims/10034028/
import os, requests
base_url = "https://patentref.io"
headers = {"X-Api-Key": os.environ["PATENTREF_KEY"]}
r = requests.get(base_url + "/api/v1/structure/claims/10034028/", headers=headers)
r.raise_for_status()
tree = r.json()
for c in tree["claims"]:
print(c["number"], c["depends_on"], c["category"])
for e in c["elements"]:
assert c["text"][e["start"]:e["end"]] == e["text"] # every offset points into the claim text
How the tree is read
- GET /structure/claims/{patent_id}/ (the trailing slash is optional): the claim tree of one granted patent, {"error": false, "patent_id", "patent_unit_id", "structure_version", "generator", "data_version", "text_data_version", "as_of", "kind", "utility_like", "claims": [...], "relations": [...], "flags": [...]}. It is built when it is asked for, from the claims the long-text file in service prints (the rows /g_claim/ answers for the same patent_id), by PatentRef's claim parser: regular expressions and small state machines, the same answer for the same text every time, no language model, nothing stored. patent_id as PatentsView prints it (10000000, D872010, RE31177, PP36776; a US prefix is read too). kind is utility, design, plant or reissue. The answer carries X-Data-Version and X-Text-Data-Version.
- A claim: {"claim_id", "number", "number_basis", "sequence", "text", "body_start", "independent", "depends_on", "depends_on_basis", "depends_on_text", "depends_on_field", "dependent_field", "references", "category", "category_basis", "preamble", "transition", "elements", "terms", "claim_refs", "exemplary", "flags"}. text is the claim as published, untouched. depends_on lists the claims a claim refers back to in its preamble ("The method of claim 5"), or, when its text names none, the ones the dependent field of the long text names; depends_on_basis says which (text, field, field_body_reference or none), and depends_on_text and depends_on_field keep both readings. references lists the claims the text cites outside the preamble. category (method, apparatus, system, composition, crm for a computer-readable medium, use, design, plant or other) is read from the words of the preamble, or taken from the parent claim when a dependent claim's own preamble names none. preamble, transition and elements are the parts of the text around its transition word ("comprising", "consisting of", "wherein" and the like; transition.phrase is the word matched); an element's kind is element or wherein_clause. terms are noun phrase mentions with role introduces ("a widget") or refers ("the widget", "said widget"); a mention that refers carries antecedent, the term_id of the earlier mention it was matched to, with match (exact, head, trim or prefix, strongest first) and found_in (same_claim, dependency_chain or cited_claim: where the earlier mention was found), or antecedent null when none was found. claim_refs are the claim citations found in the text with the claims they name (targets).
- Offsets: the preamble, the transition and every element, term mention and claim citation carry start and end, offsets into the claim's own text: text[start:end] is the part's text. They count Unicode code points (Python string indices); a UTF-16 client such as JavaScript converts before slicing text with characters outside the Basic Multilingual Plane. Every offset is checked on the server before the answer is sent.
- relations: {"src", "rel", "dst"} between the ids of those parts: part_of, depends_on, references_claim, introduces_term, refers_to_term (a mention to its antecedent), has_flag. The ids: US<patent_id> for the patent, US<patent_id>-c<n> for claim n (a repeated number gets -s<sequence>), and -pre, -tr, -e<k>, -t<k> and -f<k> after a claim id for its preamble, transition, elements, term mentions and flags.
- flags: notes on the text and the data, each {"code", "flag_id", ...} with the details of its code, on a claim or on the patent. On a claim: dependent_field_text_mismatch (the dependent field and the preamble name different claims), dependency_reference_in_body (the dependent field names claims the text cites outside the preamble), dependent_field_unparsed, multiple_dependency (more than one parent claim), dependency_on_later_claim, dependency_on_missing_claim, self_dependency, dependency_cycle, claim_reference_outside_preamble, reference_to_missing_claim, preceding_claims_reference ("any preceding claim", read by position), claim_reference_letter_read_as_one ("claim l" read as claim 1), unparsed_claim_reference, empty_element, no_transition_found, empty_preamble, empty_claim_text, claim_number_text_mismatch, duplicate_claim_number, unmatched_reference (a mention that refers with no earlier mention found). On the patent: rows_reordered_by_claim_sequence, no_independent_claim, no_claims. A flag says what the parser saw in the text as printed and nothing more.
- structure_version names the shape of the tree ("2") and goes up when the shape changes; generator names the parser and its version. It went from "1" to "2" in API 1.21.0, when the field of a term mention that says where its antecedent was found took the name found_in, with the same values; nothing else in a tree changed. Cache an answer under structure_version, data_version and text_data_version together.
- Limits and errors: the tree is built when it is asked for, and the server keeps a bounded number of recent trees in memory. A claim set over 1,000 claim rows or 1,000,000 characters, or a read over 5 s, answers 500 ERR_ES (the largest claim set in the data has 887 claims). A string that is not a patent number is 400 ERR_Q; a patent the data in service does not have, and a patent it has without claim text in the long-text file (granted after that file's last grant date), answer 404 {"error": true} with X-Status-Reason-Code NOT_FOUND and X-Status-Reason saying which; with no long-text file in service the path answers 501 ERR_NOT_IMPLEMENTED.
- Example: GET /structure/claims/10034028/ with X-Api-Key. Its claim 6, abridged (one of its three term mentions):
{"claim_id": "US10034028-c6", "number": 6, "text": "6. The method of claim 5 , wherein the caption stream is associated with the media stream.", "independent": false, "depends_on": [5], "depends_on_basis": "text", "category": "method", "preamble": {"unit_id": "US10034028-c6-pre", "text": "The method of claim 5", "start": 3, "end": 24}, "transition": {"unit_id": "US10034028-c6-tr", "text": "wherein", "start": 27, "end": 34, "phrase": "wherein"}, "elements": [{"element_id": "US10034028-c6-e1", "text": "the caption stream is associated with the media stream", "start": 35, "end": 89, "kind": "wherein_clause"}], "terms": [{"term_id": "US10034028-c6-t2", "text": "the caption stream", "start": 35, "end": 53, "role": "refers", "antecedent": "US10034028-c5-t18", "match": "exact", "found_in": "dependency_chain"}], "flags": []}
The answer
A claim
Errors keep the upstream form, with X-Status-Reason and X-Status-Reason-Code; send Accept: application/problem+json for RFC 9457 problem details. The codes are on the keys, limits and errors page and at /docs/errors/NOT_FOUND.
Patent briefs
A brief of one granted patent that fits the number of tokens you give it: its identity and dates, its status record with its basis, each independent claim as its elements with their offsets in the claim text, each dependent claim on one line, and the claim term keys, in that order, cut where the budget ends. It is made from the claim tree when it is asked for; every quote in it is the claim text at its offsets.
- Route
GET /api/v1/structure/brief/{patent_id}/?max_tokens= (300 to 32000, default 2000; the trailing slash is optional)
- Key
X-Api-Key, as every data path; it counts toward the monthly allowance
- Budget
tokens by the stated proxy ceil(utf8_bytes / 3), never over max_tokens
- Machine-readable
- the operation in openapi.json (operationId
StructureBrief, schema PatentBrief)
Example
curl -s -H "X-Api-Key: $KEY" "https://patentref.io/api/v1/structure/brief/10034028/?max_tokens=1000"
import os, requests
base_url = "https://patentref.io"
headers = {"X-Api-Key": os.environ["PATENTREF_KEY"]}
r = requests.get(base_url + "/api/v1/structure/brief/10034028/", params={"max_tokens": 1000}, headers=headers)
r.raise_for_status()
brief = r.json()
print(brief["tokens"], "of", brief["max_tokens"], "tokens; left out:", brief["omitted"])
for c in brief["independent_claims"]:
for e in c["elements"]:
print(brief["page"] + "#" + e["anchor"], e["text"]) # a link to the element on the record page
How a brief is made
- GET /structure/brief/{patent_id}/?max_tokens=<300 to 32000, default 2000> (the trailing slash is optional): a brief of one granted patent that fits max_tokens, {"error": false, "patent_id", "structure_version", "generator", "data_version", "text_data_version", "as_of", "max_tokens", "tokens", "tokenizer", "truncated", "omitted": {"independent_claims", "elements", "dependent_claims", "component_keys"}, "page", "identity", "status", "independent_claims", "dependent_claims", "component_keys"}. It is built when it is asked for, from the patent's record, its status record and its claim tree (the claim tree's parser: no language model, nothing stored). patent_id as PatentsView prints it (a US prefix is read too). The answer carries X-Data-Version and X-Text-Data-Version.
- What it holds, in this order: identity {"patent_id", "patent_title", "patent_type", "patent_date", "filing_date", "claim_count", "independent_count"} (patent_date is the grant); status, the record GET /lapse/status/{patent_id}/ answers without its links, {"status", "basis", "expiry_date_estimated", "lapse_date", "reinstated_date", "reinstatement_possible", "reinstatement_basis", "plain", "as_of"} (null when no status catalog is in service); independent_claims [{"number", "anchor", "category", "preamble", "transition", "element_count", "elements": [{"anchor", "start", "end", "text", "kind"}]}] (preamble and transition are quotes or null; kind only on a wherein_clause); dependent_claims, one line each, [{"number", "anchor", "depends_on", "start", "end", "text", "cut"}]: the claim's text from its transition, or from its body when it has none, to its end, cut at a space after at most 240 characters (cut: true, only then); component_keys [{"key", "claims"}]: the term key of each term the claims introduce (a claim tree's key: the patent's words, lower case, the last word singular) with the numbers of the claims that introduce it, ordered by the first claim that introduces it, independent claims first, then by offset.
- The cut: the items go in in that order (identity, status, each independent claim's head and then its elements one by one, each dependent line, each key), and the first item that does not fit ends the brief, so the brief for a smaller budget is the start of the brief for a larger one. truncated says whether the budget left anything out and omitted counts what it left out. tokens is the whole answer's size by the stated proxy that tokenizer names, "ceil(utf8_bytes / 3)": the UTF-8 bytes of the JSON body as sent, divided by 3 and rounded up; it is never over max_tokens. A model's own tokenizer counts differently: leave room by asking for less.
- Quotes and offsets: a preamble, a transition, every element and every dependent line carry start and end, offsets into the claim's own text as published (text[start:end] is the quote, in Unicode code points, the offsets GET /structure/claims/{patent_id}/ serves), and every one is checked on the server before the answer is sent. The title, the quoted text and the keys are the patent's own words, as printed. Nothing in a brief is a conclusion about a claim.
- Limits and errors: a string that is not a patent number, and a max_tokens that is not a whole number from 300 to 32000, are 400 ERR_Q; a patent the data in service does not have, and one without claim text in the long-text file (granted after that file's last grant date), answer 404 {"error": true} with X-Status-Reason-Code NOT_FOUND and X-Status-Reason saying which; a claim set over the claim tree's work cap answers 500 ERR_ES; with no long-text file in service the path answers 501 ERR_NOT_IMPLEMENTED; 503 ERR_ES when the status records cannot be read. The server keeps a bounded number of recent briefs in memory.
- Example: GET /structure/brief/10034028/?max_tokens=1000 with X-Api-Key. Abridged (identity, status and four of the six elements it holds left out here):
{"error": false, "patent_id": "10034028", "max_tokens": 1000, "tokens": 963, "tokenizer": "ceil(utf8_bytes / 3)", "truncated": true, "omitted": {"independent_claims": 2, "elements": 17, "dependent_claims": 15, "component_keys": 53}, "page": "https://patentref.io/p/10034028?v=20261027.2", "independent_claims": [{"number": 1, "anchor": "c1", "category": "method", "preamble": {"start": 3, "end": 78, "text": "A method in a computer system for caption synchronization of media programs"}, "transition": {"start": 79, "end": 89, "text": "comprising"}, "element_count": 11, "elements": [{"anchor": "c1-e1-90-211", "start": 90, "end": 211, "text": "receiving at least one portion of a media stream and at least one portion of a caption data stream in the computer system"}, {"anchor": "c1-e2-213-344", "start": 213, "end": 344, "text": "wherein the at least one portion of the media stream and the at least one portion of the caption data stream correspond to an event", "kind": "wherein_clause"}]}], "dependent_claims": [], "component_keys": []}
The answer
An independent claim in a brief
A dependent claim in a brief
Links to claims and elements
- Links to claims and elements: the record page https://patentref.io/p/<patent_id> shows the patent's claims below its record, each claim's text up to its first element and then one line for each element, and gives each claim and each element an anchor: c<n> for claim n (c<n>-s<claim_sequence> when a number repeats in the data) and c<n>-e<k>-<start>-<end> for element k of claim n, with its start and end in the claim text. The page plus "#" plus an anchor opens that claim or element, for example https://patentref.io/p/10034028#c1-e4-462-602 (a brief's page names the data version too). The same data gives the same anchors on every build. When the data changes and an anchor is no longer on the page, a link to it opens the claim it names, with a note saying so; when the page has no such claim, the claims heading, with the note.
Triage table
Up to 500 patents in one answer: each patent's expiry and fee status with the basis, where each component sits in its claims, and each independent claim as a list of its parts, every quote with its offsets in the claim text. The page /triage/ draws the table with your key and keeps your have and lacks marks in your browser; an example table needs no key.
- Route
POST /api/v1/structure/triage/ (a JSON body), or GET with the same fields as parameters
- Key
X-Api-Key, as every data path; one request toward the monthly allowance, whatever the number of patents
- Built
- when it is asked for, by the claim tree's parser (no language model, nothing stored)
- Machine-readable
- the operations in openapi.json (operationId
StructureTriage and StructureTriagePost, schema TriageTable)
curl -s -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{"patents": "11773614 11649651 8696821 9051750", "components": "drive motor, brush"}' https://patentref.io/api/v1/structure/triage/
curl -s -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{"search": "\"pool cleaner\" brush", "limit": 50, "output": "csv"}' https://patentref.io/api/v1/structure/triage/ -o table.csv
How the table is read
- POST /structure/triage/ (GET takes the same fields as parameters; the trailing slash is optional): {"patents": [...] or "<pasted text>"} or {"search": "<words>", "limit": <1 to 500, default 100>}, with "components" (optional) and "output" ("json", the default, or "csv"). The answer: {"error": false, "data_version", "text_data_version", "as_of", "structure_version", "generator", "input", "components": [{"key", "from", "asked", "patents"}], "positions", "complete", "time_limit_seconds", "status_available", "counts": {"patents", "found", "built", "not_built"}, "quotes", "export_columns", "patents": [...]}. It carries X-Data-Version and X-Text-Data-Version. A list of 500 numbers is too long for a GET request line: send it by POST.
- The patents: a list of up to 500 numbers (a JSON list, or text with the numbers separated by spaces, commas, semicolons or new lines; US, thousands separators such as 10,905,426, hyphens and kind codes such as B2 are read, and pieces that are not patent numbers are listed in input.unread), in the order given, each once; or a search: the claims that hold every word of search (at most 12 words and "quoted phrases"), read from the full-text index of the claims in the reverse of the order the long-text file loaded them (the bulk files year by year, oldest first, so recent grants tend to come first), each patent once, the first limit of them. input.more says more patents match; input.complete is false when the search stopped at its 5 s.
- A row: {"row", "patent_id", "title", "patent_date", "permalink", "found", "built", "note", "status", "claims": {"total", "independent"}, "cells", "components", "claim_parts"}. status holds status, basis, reinstatement_possible, reinstatement_basis, expiry_date_estimated, lapse_date, reinstated_date and as_of: the values GET /lapse/status/{patent_id}/ gives (null for a number the data in service does not have). note says why a row has no cells: not_in_data, no_claim_text (granted after the long-text file's last grant date), work_cap (over the claim tree's 1,000 claim rows or 1,000,000 characters), offset_check (a span that did not hold) or time_limit (below).
- Components: up to 12 in components, each read as a claim tree's term key (the noun phrase the term finder reads, lower case, the last word singular: "drive motors" is drive motor; words it does not read as a noun phrase stay as written); without any, the table picks up to 6: the terms the most of its patents introduce, each in at least two of them, weighed by ln(N / df) of the term's rarest word in titles and abstracts. components[].from is request or table, and patents counts the table's patents whose claims mention it.
- A cell, one per component in the table's order: {"key", "position", "match", "claim_keys", "independent_claims", "dependent_claims", "quote"}. position: every_independent (each independent claim mentions the component), some_independent (some independent claims do), only_dependent (no independent claim does, and a dependent claim does) or absent (no claim does). A claim mentions a component when one of its term mentions (an "a" or "the" phrase of the claim tree) has the component's key, or refers back to a mention that has it ("the device" matched to "an electronic device"). match is exact (the same key) or partial (one key ends with the other as whole words: drive motor and electric drive motor), and claim_keys names the claim terms' keys it matched. quote is the first mention, in the first independent claim that has one, else the first claim: {"claim", "claim_id", "term_id", "start", "end", "text"}.
- A row's components: the keys of the terms its own claims introduce, grouped as every_independent, some_independent and only_dependent. claim_parts: each independent claim as {"claim", "claim_id", "category", "parts": [{"part", "claim", "claim_id", "part_id", "start", "end", "text"}]}, part preamble, transition, element or wherein_clause in the order of the claim's text (an element can add label, its own label in the text, and opens_list); part_id is the part's id in the claim tree of GET /structure/claims/{patent_id}/.
- Quotes: text is the claim's text from start to end (Unicode code points, as in the claim tree), and the server checks every quote against the claim text before the answer leaves; quotes counts them.
- output=csv: the export, RFC 4180 (comma, CRLF, a field quoted when it holds a quote, a comma or a line break), UTF-8 without a byte order mark, the columns of export_columns: patent_id, title, status, status_basis (the basis codes joined by semicolons), reinstatement_possible, as_of, note, row_kind (patent, component, preamble, transition, element or wherein_clause), component, position, match, claim, claim_id, part_id, label, quote, start, end, mark. One row per patent, one per component of each row with claims, and one per claim part. The mark column is empty here: have and lacks are the reader's own notes, which the page https://patentref.io/triage/ keeps in the browser and writes into its own export (the same columns); the server never receives a mark.
- Limits and errors: one table at a time per key, and a few at once on the server; beyond that 429 with Retry-After and an X-Status-Reason that says so. A table stops reading claims after 20 s: the rows it did not reach say note time_limit and complete is false; send them again (trees a server process already read come back faster). One request toward the monthly allowance, whatever the number of patents. A request the table does not read is 400 ERR_Q, X-Status-Reason saying which field; with no long-text file in service the path answers 501 ERR_NOT_IMPLEMENTED, as a search does when that file has no full-text index of the claims. The table describes the text of the claims and nothing more.
The answer
A row
A cell