{
  "openapi": "3.1.0",
  "info": {
    "title": "Antecedint API",
    "version": "0.1.0",
    "description": "Prior-art retrieval, patent-space geometry, and §112 claim validation over 10.5M US patent applications. Every endpoint takes an API key as a bearer token. The same surface is available over MCP at https://mcp.antecedint.com/api/v1/mcp.",
    "contact": {
      "name": "Antecedint",
      "url": "https://antecedint.com"
    }
  },
  "servers": [
    {
      "url": "https://mcp.antecedint.com/api/v1"
    }
  ],
  "tags": [
    {
      "name": "Prior-art search",
      "description": "Two-stage neural retrieval over 10.5M US applications."
    },
    {
      "name": "Patent space",
      "description": "Positions, neighbours, citations and gaps in one shared coordinate frame."
    },
    {
      "name": "§112 claim validation",
      "description": "Antecedent basis and claim-term support, for drafts and for filed applications."
    },
    {
      "name": "Warm-up",
      "description": "Start the inference hub before a search needs it, so the cold start is paid off the critical path."
    },
    {
      "name": "Prosecution intelligence",
      "description": "CPC scheme and empirical routing, art-unit behaviour, and examiner statistics."
    },
    {
      "name": "Examination guidance",
      "description": "The MPEP and the EPO Guidelines, the graph of how their sections cite each other, and the form paragraphs the USPTO pastes into office actions."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key issued by Antecedint, sent as `Authorization: Bearer ak_live_...`."
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/prior-art": {
      "post": {
        "operationId": "postPriorArt",
        "summary": "Search 10.5M US patents for prior art against a claim",
        "description": "Semantic prior-art search for a claim set. A fine-tuned dense encoder retrieves\ncandidates from the Index-1 vector space, and a citation-graph channel contributes\nreferences an embedding alone would miss.\n\nEach result carries its title, abstract and claims. It does not carry a specification\nbody: this used to promise background, summary and detailed description, which was true\nwhile the route downloaded each hit's SPEC document from USPTO and ran OCR and a model\nover it on the request path. That cost 18-55s per search and came off the live path on\n2026-08-24. Context now comes from the corpus we already hold, and `description_text`\nis unpopulated there, so those sections are not available at any latency.\n\n`metadata.context_coverage` reports how many results actually carried each part, so a\nthin answer is distinguishable from a failed lookup.\n\nEither `claims` or `abstract` satisfies the request, but claim text retrieves\nbetter: the encoder was trained on `abstract [SEP] claim1`, so supplying both\nmatches the document representation the index was built from.\n\n`metadata.total_cost` reports what the call actually spent on model inference,\nin USD. Latency runs 20-60s uncached and is dominated by specification parsing,\nnot by retrieval; `rerank` adds to it.",
        "tags": [
          "Prior-art search"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ success: true, data: { specifications[], metadata } }. metadata carries total_cost in USD, request_id, and timestamp."
          },
          "400": {
            "description": "Neither claims nor abstract was supplied, both were empty, count fell outside 1-100, forceReprocess was not a boolean, or a date bound was not an ISO date. A malformed date is refused rather than dropped: a prior-art search that silently loses its cutoff returns references that postdate the invention while appearing to have honoured the request. The body is { success: false, error } with no code field."
          },
          "500": {
            "description": "Search failed. The body carries request_id; quote it when reporting."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "claims": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 20000,
                    "description": "Claim text; independent claim 1 works best. Required unless `abstract` is given."
                  },
                  "abstract": {
                    "type": "string",
                    "maxLength": 10000,
                    "description": "Abstract or invention summary. Required unless `claims` is given."
                  },
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "description": "Number of references to return.",
                    "default": 50
                  },
                  "useReranking": {
                    "type": "boolean",
                    "description": "Run the ColBERT late-interaction reranker over the top candidates. Higher precision, slower.",
                    "default": false
                  },
                  "includeClaims": {
                    "type": "boolean",
                    "description": "Return full claim text and abstract sections for every reference. Off by default: the lookup is a BigQuery query with a job-submission floor of roughly a second whatever it scans, and a caller that ranks first and reads one or two pays that second for thirteen references it never opens. Fetch the ones you want with `POST /prior-art/claims`. While this is off, `title_source` on each result reads `namespace`, meaning the title came from the vector index rather than the authoritative lookup and may name an unrelated patent.",
                    "default": false
                  },
                  "hyde": {
                    "type": "boolean",
                    "description": "Rewrite the query into patent language before searching. A model drafts a hypothetical abstract and first claim for what you described, and that is searched instead. Worth setting when you have a description rather than claim text: the index holds formal claim language, and a plain-English query has to cross that gap unaided. Not worth setting when you already have a claim, which is the strongest query available. Adds about two seconds. `metadata.hyde` returns the generated text, because a rewritten query changes what was searched and the results cannot be judged without it.",
                    "default": false
                  },
                  "hybrid": {
                    "type": "boolean",
                    "description": "Also run BM25 over patent titles and abstracts and fuse the rankings. The encoder was trained on meaning, so it does not guarantee an exact phrase outranks a paraphrase; when you use a term of art, that term is the strongest signal available and dense search alone discards it. Measured: US-11389765-B2 was absent from the dense top 20 for a gyroid heat exchanger query and is rank 1 on BM25 over titles. Each result gains `matched_legs`, how many of the three retrievers found it, which is a sharper signal than a similarity score compressed into a 0.05 band.",
                    "default": false
                  },
                  "forceReprocess": {
                    "type": "boolean",
                    "description": "Bypass and overwrite every cache layer, re-parsing specifications that were already parsed. Costs real model spend on each call and is meant for reprocessing after a parser change, not for normal use.",
                    "default": false
                  },
                  "filedBefore": {
                    "type": "string",
                    "description": "Exclude references filed on or after this date. Prior art must predate the application it is cited against, so supply the priority date of the invention you are searching for; without it the results include later filings, which cannot be cited. `YYYY`, `YYYY-MM` or `YYYY-MM-DD`. Precision depends on the backend and `metadata.filter` reports what was applied: the turbopuffer index stores filing year only, so a mid-year bound admits later filings from the same year. It errs toward showing too much rather than too little."
                  },
                  "filedAfter": {
                    "type": "string",
                    "description": "Exclude references filed on or before this date. For narrowing to a period, not for prior art."
                  },
                  "cpc3": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Restrict to these CPC subclasses, e.g. `[\"H01M\",\"F28D\"]`. Useful for a second pass once a first search has shown which art areas matter. Note that CPC data covers roughly 65% of the index, and a reference carrying no CPC is excluded by this filter rather than ranked lower — so narrowing by subclass also drops everything unclassified. Prefer it for a follow-up pass, not a first search."
                  },
                  "country": {
                    "type": "string",
                    "description": "Restrict to one country code, e.g. `US`."
                  }
                }
              }
            }
          }
        },
        "x-cost": "metered",
        "x-mcp-tool": "search_prior_art",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/prior-art\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"claims\":\"1. A method of cooling a battery pack, comprising...\",\"count\":25}'"
          }
        ]
      }
    },
    "/prior-art/claims": {
      "post": {
        "operationId": "postPriorArtClaims",
        "summary": "Fetch claim text and abstracts for specific applications",
        "description": "Full text for references you already have.\n\nWHY THIS IS SEPARATE FROM THE SEARCH\nThe claims lookup is a BigQuery query, and BigQuery charges roughly a second of job\nsubmission whatever it scans. Folding it into every search made the ranking wait on text\nthe caller had not asked for yet: fifteen references fetched in full so one or two could\nbe read. Splitting it lets a search answer in the time the search actually takes, and\nputs the second on whoever wants the text.\n\nBatched deliberately. One call for the three references worth reading costs the same\nsecond as one call for one, and the alternative is three round trips that each pay it.\n\nEndpoint docs live in the @endpoint block; see docs/api/doc-comments.md.",
        "tags": [
          "Prior-art search"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ success: true, data: { specifications[], metadata } }. One entry per application number that was found, in the order asked for. An application with no row in the lookup table is reported in metadata.not_found rather than returned empty, because \"this patent has no claims on file\" and \"we could not fetch them\" are opposite conclusions."
          },
          "400": {
            "description": "appNumbers was missing, empty, not an array, held something that is not a digit string, or held more than 25 entries."
          },
          "500": {
            "description": "The lookup failed. The body carries request_id."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "appNumbers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "US application numbers to fetch, digits only (`16115156`), 1 to 25 per call. These are the `application_number` values a search returns."
                  },
                  "format": {
                    "type": "string",
                    "description": "`parsed` returns claims only as the structured `claims` array. `raw` also includes the unparsed claim blob as a section, which is the same text a second time and roughly doubles the response. Use `raw` only when the parse itself is in question.",
                    "default": "parsed"
                  },
                  "independentOnly": {
                    "type": "boolean",
                    "description": "Return only independent claims. Triage usually needs claim 1, and a record can carry two dozen. The number dropped is reported as `metadata.claims_withheld` so a short answer cannot be mistaken for a short patent.",
                    "default": false
                  }
                },
                "required": [
                  "appNumbers"
                ]
              }
            }
          }
        },
        "x-cost": "metered",
        "x-mcp-tool": "get_patent_claims",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/prior-art/claims\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"appNumbers\":[\"16115156\",\"17003222\"]}'"
          }
        ]
      }
    },
    "/patent-map/patent/{appNum}": {
      "get": {
        "operationId": "getPatentMapPatentAppNum",
        "summary": "Patent position and its nearest semantic neighbours",
        "description": "One call frames a patent's neighbourhood, which is the payload behind the\ndisplay-mode embed: `{ self, neighbors[], count }`.\n\n`self` is null when the application has no vector in the index, which happens for\npre-corpus, design, and plant applications. The application can still be absent\nentirely, and that answers 404 rather than a null `self`.\n\nNeighbour order is cosine similarity in the embedding space, not 3D distance.\nSemantically closest comes first; the coordinates are for laying them out, not for\nre-deriving the ranking. Distance in the projection and rank in the list disagree\noften enough that sorting by one and labelling it the other will mislead.",
        "tags": [
          "Patent space"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ appNum, self, neighbors[], count, timestamp }. Each neighbour is a point plus score, the cosine similarity."
          },
          "400": {
            "description": "VALIDATION_ERROR: appNum is not 7-12 digits, or topK is outside 1-100."
          },
          "404": {
            "description": "NOT_INDEXED: No vector is indexed for this application."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The vector index could not be reached."
          }
        },
        "parameters": [
          {
            "name": "appNum",
            "in": "path",
            "required": true,
            "description": "US application number, digits only.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{7,12}$"
            }
          },
          {
            "name": "topK",
            "in": "query",
            "required": false,
            "description": "Number of neighbours to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_patent_neighborhood",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-map/patent/16123456?topK=5\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-map/patent/{appNum}/citations": {
      "get": {
        "operationId": "getPatentMapPatentAppNumCitations",
        "summary": "Cited prior art resolved to patent-space positions",
        "description": "The application's cited prior art, resolved to map positions. Each point carries\n`examiner: true` when the reference came from the examiner rather than from the\napplicant's IDS, which is the distinction that matters: examiner citations are\nwhat was actually applied against the application.\n\nExaminer-cited references lead the list and are never dropped. Applicant citations\nfill the remainder up to 150 references total, so a 400-reference IDS cannot\nballoon the response.\n\nPass `year`, the citing application's filing year, when you know it. It narrows the\nunderlying lookup and makes the call measurably cheaper.\n\nReferences with no vector in the index are counted in `notIndexed` and omitted from\n`points`. Positions on this API are always exact index coordinates; nothing is\nplaced approximately to fill a gap.",
        "tags": [
          "Patent space"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ appNum, points[], count, citedTotal, notIndexed, timestamp }. Each point carries examiner and title alongside the coordinates."
          },
          "400": {
            "description": "VALIDATION_ERROR: appNum is not 7-12 digits, or year is implausible."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The citation service or vector index could not be reached."
          }
        },
        "parameters": [
          {
            "name": "appNum",
            "in": "path",
            "required": true,
            "description": "US application number, digits only.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{7,12}$"
            }
          },
          {
            "name": "year",
            "in": "query",
            "required": false,
            "description": "Filing year of the citing application. Optional, and makes the lookup faster when supplied.",
            "schema": {
              "type": "integer",
              "minimum": 1970,
              "maximum": 2035
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_patent_citations",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-map/patent/16123456/citations?year=2019\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-map/locate": {
      "post": {
        "operationId": "postPatentMapLocate",
        "summary": "Locate free text or an application in patent space",
        "description": "Semantic search into the space. Free text is embedded and matched against the\nindex; given an `appNum` instead, the application's own abstract and first claim\nare joined as `abstract [SEP] claim1` and used as the query, which reproduces the\ndocument representation Index-1 was built from and retrieves better than anything\ntyped by hand.\n\nAlongside the nearest patents it returns `landing`, a rank-weighted centroid of the\ntop 8 hits. That is where the query sits in the space and the anchor a camera should\nfly to. It is not a patent and has no application number.\n\nThis is the one endpoint on the patent-map router that spends an embedding-hub\nencode per call, so it is the one that does not answer from cache.",
        "tags": [
          "Patent space"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ landing: {x,y,z}, self?, neighbors[], count, located_by, timestamp }. located_by is stored_vector (the index holds the application: landing is its stored point, returned as self, and neighbour scores are against the document vector), re_encoded_text (it does not: the text was encoded as a query, scores top out near 0.87 and landing is a centroid; a note says so), or query_text."
          },
          "400": {
            "description": "VALIDATION_ERROR: Neither query nor appNum was supplied, or one of them failed its length or format rule."
          },
          "404": {
            "description": "NOT_FOUND: No text could be found for the application, or no indexed match carried map coordinates."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The embedding hub or vector index could not be reached."
          },
          "503": {
            "description": "MAP_COORDINATES_UNAVAILABLE: The vector index this deployment is configured against does not store UMAP coordinates, so nothing can be placed in patent space. Prior-art search is unaffected."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 4000,
                    "description": "Free-text technical description. Supply either this or `appNum`; `query` wins when both are present."
                  },
                  "appNum": {
                    "type": "string",
                    "pattern": "^\\d{7,12}$",
                    "description": "Locate an application by its stored vector. The application itself is excluded from `neighbors`."
                  },
                  "topK": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "description": "Number of nearest patents to return.",
                    "default": 20
                  }
                }
              }
            }
          }
        },
        "x-cost": "metered",
        "x-mcp-tool": "locate_in_patent_space",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-map/locate\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"query\":\"phase-change material for battery thermal management\",\"topK\":10}'"
          }
        ]
      }
    },
    "/patent-map/whitespace": {
      "get": {
        "operationId": "getPatentMapWhitespace",
        "summary": "Ranked gaps in patent space",
        "description": "The analysed whitespace atlas: gaps in patent space, ranked by opportunity,\ndescending. Built for the inventor-facing question of where the open ground is.\n\nEach void carries an id, a position in the same coordinate frame as every other\nendpoint on this router, a radius, an opportunity score from 0 to 1, a CPC section,\nthe bordering CPC codes with their fractions and titles, a temporal profile giving\nthe median filing year of the surrounding art and a `hot` flag, a generated summary\nof what is missing there, the nearest bordering patents as exemplars, and the\nassignees active nearby.\n\nThe atlas is a precomputed artifact, rebuilt on its own schedule rather than on\nrequest, and cached for 30 minutes once loaded. Two calls a minute apart return the\nsame voids.",
        "tags": [
          "Patent space"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ voids[], count, total, timestamp }. total counts everything matching the filters; count is how many limit allowed through."
          },
          "400": {
            "description": "VALIDATION_ERROR: limit, section, or minOpportunity is out of range."
          },
          "404": {
            "description": "NOT_FOUND: The whitespace atlas has not been built."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The atlas could not be read from storage."
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum voids to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "section",
            "in": "query",
            "required": false,
            "description": "Restrict to one CPC section letter.",
            "schema": {
              "type": "string",
              "pattern": "^[A-HY]$"
            }
          },
          {
            "name": "minOpportunity",
            "in": "query",
            "required": false,
            "description": "Drop voids scoring below this.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0
            }
          }
        ],
        "x-cost": "free",
        "x-cache-control": "private, max-age=1800",
        "x-mcp-tool": "find_whitespace",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-map/whitespace?section=G&minOpportunity=0.6&limit=20\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-map/points": {
      "get": {
        "operationId": "getPatentMapPoints",
        "summary": "Sampled background point cloud",
        "description": "A sampled background cloud for context rendering, up to 50,000 points per request.\n\nThe sample pages the index in stored order. It is stable across calls, and it is not\nrandom: the first 10,000 points are the same 10,000 points every time, and they are\nnot a representative sample of the corpus. Filter server-side with `section` and\n`artUnit` rather than over-fetching and filtering client-side, because the scan stops\nat a fixed budget regardless of how many points survive your filter.\n\nThat budget is roughly 150,000 scanned vectors. A filter narrow enough to exhaust it\nbefore collecting `limit` points returns what it found with `truncated: true`. Treat\nthat flag as a signal to narrow differently rather than to retry, since the scan is\ndeterministic and a retry returns the same partial sample.",
        "tags": [
          "Patent space"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ points[], count, truncated, timestamp }"
          },
          "400": {
            "description": "VALIDATION_ERROR: limit, section, or artUnit is out of range."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The vector index could not be reached."
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Points to return.",
            "schema": {
              "type": "integer",
              "minimum": 100,
              "maximum": 50000,
              "default": 10000
            }
          },
          {
            "name": "section",
            "in": "query",
            "required": false,
            "description": "Restrict to one CPC section letter.",
            "schema": {
              "type": "string",
              "pattern": "^[A-HY]$"
            }
          },
          {
            "name": "artUnit",
            "in": "query",
            "required": false,
            "description": "Restrict to an art-unit prefix.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,4}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=1800",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-map/points?limit=5000&section=H\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/claim-validation/antecedent-basis": {
      "post": {
        "operationId": "postClaimValidationAntecedentBasis",
        "summary": "Check a draft claim set for antecedent-basis defects",
        "description": "Check a claim set for §112(b) antecedent-basis defects. Finds definite recitations\n(\"the widget\", \"said widget\") with no earlier indefinite recitation in the same claim\nor in a claim it depends from, plus double inclusions, plural and singular mismatches,\nand improper dependencies.\n\nImplicit antecedent basis under MPEP 2173.05(e) is suppressed, so \"the outer surface\nof the housing\" is not flagged when the housing was properly introduced. Pass\n`includeSuppressed` to see what the suppression rules removed and which rule removed\neach one, which is how you audit the checker rather than trust it.\n\nFindings are graded. `high` means no plausible antecedent exists anywhere in the set;\n`low` means one probably exists under different wording and a human should look.\n\nDeterministic, no LLM, no network, and sub-millisecond per claim, so a draft editor can\ncall it on every keystroke.",
        "tags": [
          "§112 claim validation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ available: true, source: \"request\", issues[], claims[], dependencies[], stats }. issues[] is the flat list of every defect found; each entry of claims[] also carries its own issues[]. stats holds issueCount, bySeverity and byType. An empty issues[] means the claim set is clean, and is the only thing that means that."
          },
          "400": {
            "description": "No claim in claims[] carried usable text, or minSeverity was not one of high, medium, low. The body is { error } with no code field. An unrecognised minSeverity is refused rather than defaulted, because silently returning the unfiltered list looks like a filtered one."
          },
          "500": {
            "description": "The check itself failed."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "claims": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "text": {
                          "type": "string",
                          "minLength": 10,
                          "description": "Claim text without the leading number."
                        },
                        "number": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "Claim number."
                        }
                      },
                      "required": [
                        "text"
                      ]
                    },
                    "maxItems": 300,
                    "description": "The claim set, in order. Each entry is `{ number?, text }`; a bare string is accepted in place of an object, and a missing `number` is filled from the array position."
                  },
                  "minSeverity": {
                    "type": "string",
                    "enum": [
                      "high",
                      "medium",
                      "low"
                    ],
                    "description": "Lowest severity to report.",
                    "default": "low"
                  },
                  "includeSuppressed": {
                    "type": "boolean",
                    "description": "Also return the candidates the implicit-basis rules suppressed, each with the rule that killed it.",
                    "default": false
                  }
                },
                "required": [
                  "claims"
                ]
              }
            }
          }
        },
        "x-cost": "free",
        "x-mcp-tool": "check_antecedent_basis",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/claim-validation/antecedent-basis\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"claims\":[{\"number\":1,\"text\":\"A device comprising a housing.\"},\n  {\"number\":2,\"text\":\"The device of claim 1, wherein the lid is hinged.\"}]}'"
          }
        ]
      }
    },
    "/claim-validation/antecedent-basis/{appNum}": {
      "get": {
        "operationId": "getClaimValidationAntecedentBasisAppNum",
        "summary": "Check the filed claims of an application",
        "description": "The same check as POST /antecedent-basis, run over the claims already on file for an\napplication rather than over claims you supply.\n\nAn application whose claims are not in the warehouse answers 200 with\n`{ available: false, reason: \"no-claims\" }`, not 404. The distinction is deliberate:\nthe application exists and the request was well formed, we just have nothing to check.\nBranch on `available` before reading `issues`.",
        "tags": [
          "§112 claim validation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ appNum, available: true, source: \"warehouse\", issues[], claims[], dependencies[], stats }, shaped exactly as the POST above, or { appNum, available: false, reason: \"no-claims\" } when nothing is on file."
          },
          "400": {
            "description": "appNum is not a 7-8 digit US application number, or minSeverity was not one of high, medium, low."
          },
          "502": {
            "description": "The claim pull from the warehouse failed."
          }
        },
        "parameters": [
          {
            "name": "appNum",
            "in": "path",
            "required": true,
            "description": "US application number, digits only.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{7,8}$"
            }
          },
          {
            "name": "minSeverity",
            "in": "query",
            "required": false,
            "description": "Lowest severity to report.",
            "schema": {
              "type": "string",
              "enum": [
                "high",
                "medium",
                "low"
              ],
              "default": "low"
            }
          },
          {
            "name": "includeSuppressed",
            "in": "query",
            "required": false,
            "description": "Send `1` to include suppressed candidates with the rule that removed each.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "check_antecedent_basis",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/claim-validation/antecedent-basis/16123456?minSeverity=high\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/claim-validation/support": {
      "post": {
        "operationId": "postClaimValidationSupport",
        "summary": "Map claim terms to their specification support",
        "description": "Map every claim term to the specification passages that give it meaning, with exact\nlocations: section, paragraph number, and character offsets.\n\nEach term is graded by how the spec supports it, not merely whether the word appears.\nA term can be formally defined, defined by the claim itself, scoped by enumeration,\nmapped to an embodiment, characterized functionally, merely described, or UNDESCRIBED.\nThat last grade is the §112(a) written-description signal worth acting on.\n\n`claims[]` is always required on this endpoint. Use GET /support/:appNum when you want\nthe filed claims analyzed instead of your own.\n\nSupply `sections` and the analysis runs against your own specification text and touches\nno network, which is what makes this usable on an unfiled draft. Supply `appNum` without\n`sections` and the filed specification is pulled for you. Supplying `appNum` with\n`includeParents` walks the continuity chain and checks each parent specification too, so\na term supported here but in no ancestor is surfaced as new matter that cannot claim the\nparent's filing date. The walk needs `appNum`; it cannot run on text alone.",
        "tags": [
          "§112 claim validation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Per-term support grades with locations, plus the ancestor results when the continuity walk ran."
          },
          "400": {
            "description": "claims[] was empty, or neither sections[] nor appNum was supplied."
          },
          "500": {
            "description": "The analysis failed."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "claims": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "maxItems": 300,
                    "description": "Claim set to analyze, each `{ number?, text }`. Always required here; GET /support/:appNum uses the filed claims instead."
                  },
                  "sections": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "maxItems": 60,
                    "description": "Specification sections to ground against, each `{ type?, name?, text }`. Either this or `appNum` is required."
                  },
                  "appNum": {
                    "type": "string",
                    "pattern": "^\\d{7,8}$",
                    "description": "US application number. Required for the continuity walk, and used to pull the spec when `sections` is omitted."
                  },
                  "includeParents": {
                    "type": "boolean",
                    "description": "Walk the continuity chain and check each parent specification. Ignored without `appNum`.",
                    "default": false
                  },
                  "maxAncestors": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 12,
                    "description": "Cap on ancestors to check.",
                    "default": 6
                  },
                  "ocr": {
                    "type": "boolean",
                    "description": "Allow OCR for image-only filings. Most provisionals are image-only, so the walk usually needs this to read them.",
                    "default": false
                  }
                },
                "required": [
                  "claims"
                ]
              }
            }
          }
        },
        "x-cost": "metered",
        "x-mcp-tool": "map_claim_support",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/claim-validation/support\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"appNum\":\"16123456\",\"includeParents\":true,\"ocr\":true,\n  \"claims\":[{\"number\":1,\"text\":\"A battery pack comprising a phase-change layer.\"}]}'"
          }
        ]
      }
    },
    "/claim-validation/support/{appNum}": {
      "get": {
        "operationId": "getClaimValidationSupportAppNum",
        "summary": "Support map for a filed application and its parents",
        "description": "The support map for an application already on file, using its filed claims and its\nfiled specification. No claim text is supplied by the caller.\n\nThe continuity walk runs by default here, which is the opposite of POST /support. Send\n`parents=0` to analyze the application's own specification alone. The walk is what makes\nthis call slow and what makes it worth making: it is where new matter shows up.\n\nOCR is opt-in with `ocr=1` and is usually needed for the walk to be meaningful, because\nmost provisionals are image-only scans with no extractable text layer. Without it an\nunreadable ancestor contributes nothing, and a term it actually supports can be reported\nas new matter. The flag is also killable server-side: when `SPEC_OCR_DISABLED` is set,\n`ocr=1` is ignored rather than refused.",
        "tags": [
          "§112 claim validation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Per-term support grades with locations, plus per-ancestor results and any new-matter findings when the continuity walk ran."
          },
          "400": {
            "description": "appNum is not a 7-8 digit US application number."
          },
          "502": {
            "description": "The specification pull or the analysis failed."
          }
        },
        "parameters": [
          {
            "name": "appNum",
            "in": "path",
            "required": true,
            "description": "US application number, digits only.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{7,8}$"
            }
          },
          {
            "name": "parents",
            "in": "query",
            "required": false,
            "description": "Send `0` to skip the continuity walk.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "maxAncestors",
            "in": "query",
            "required": false,
            "description": "Cap on ancestors to check.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 12,
              "default": 6
            }
          },
          {
            "name": "ocr",
            "in": "query",
            "required": false,
            "description": "Send `1` to allow OCR on image-only filings.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "map_claim_support",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/claim-validation/support/16123456?ocr=1&maxAncestors=4\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/warmup": {
      "post": {
        "operationId": "postWarmup",
        "summary": "Start the inference hub ahead of a search",
        "description": "Start the inference hub before you need it.\n\nThe hub scales to zero, so the first search after an idle period pays ~71s of model\nloading. Call this a minute or two ahead and that cost is paid off the critical path;\nthe search that follows runs at its warm latency of a few seconds.\n\nThe call returns immediately whether or not the hub is ready — it never blocks for the\ncold start. Poll `GET /warmup` to watch it become ready, or simply proceed and accept\nwhatever state the hub is in.",
        "tags": [
          "Warm-up"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ ready, warming, models, estimatedReadySeconds, warmWindowSeconds }. ready true means a warm instance exists now; it is not a reservation."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The inference hub could not be reached at all."
          }
        },
        "parameters": [
          {
            "name": "rerank",
            "in": "query",
            "required": false,
            "description": "Also load the reranker, which `search_prior_art` needs when `useReranking` is set. Adds ~25s to the warm-up and is unnecessary for retrieval alone.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Seconds to wait for the models to finish loading before answering, up to 120. The default of 0 returns straight away and leaves the caller to poll. Set this and the call blocks until `ready` is true or the wait runs out, which is one round trip instead of a polling loop.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "x-cost": "free",
        "x-cache-control": "no-store",
        "x-mcp-tool": "warm_up",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS -X POST \"$BASE/warmup?rerank=true\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      },
      "get": {
        "operationId": "getWarmup",
        "summary": "Whether the inference hub is warm",
        "description": "Whether the inference hub is warm right now.\n\nRead-only: it reports state and starts nothing. Use it to poll after `POST /warmup`,\nor to decide whether a search is about to be fast before committing to it.",
        "tags": [
          "Warm-up"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ ready, models, warmWindowSeconds }. ready reflects the encoder only; check models.reranker if the next call sets useReranking."
          },
          "502": {
            "description": "UPSTREAM_ERROR: The inference hub could not be reached at all."
          }
        },
        "x-cost": "free",
        "x-cache-control": "no-store",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/warmup\" -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-intel/cpc/{code}": {
      "get": {
        "operationId": "getPatentIntelCpcCode",
        "summary": "CPC symbol with its art-unit distribution and children",
        "description": "One CPC symbol, resolved three ways at once: what the scheme says it means,\nwhich art units actually examine it, and what sits under it.\n\nThe art-unit distribution is empirical rather than declared — it is where\napplications carrying this symbol were really routed, with the modal unit, the\nper-unit probabilities and the Shannon entropy of the spread. A low entropy\nmeans the symbol predicts the art unit; a high one means it does not, which is\nthe more useful answer when deciding whether to argue classification.\n\n`children` is capped by `childLimit`, because a subclass can carry thousands of\nsubgroups and a caller asking about `C11D` wants to know that it has children,\nnot to receive all of them. `child_count` always reports the true total. The\nart-unit `distribution` is capped the same way by `distributionLimit`: it is\nsorted by count, `unique_art_units` is the true total, and a symbol examined by\n222 units does not need all 222 listed to show that one of them takes 37%.",
        "tags": [
          "Prosecution intelligence"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ code, scheme, art_units, children[], child_count }. scheme is null for a symbol with routing data but no scheme entry; art_units is null for a symbol nothing has been routed under."
          },
          "400": {
            "description": "VALIDATION_ERROR: code is not a CPC symbol, or a limit is outside 0-500."
          },
          "404": {
            "description": "NOT_FOUND: Neither the scheme nor the routing data knows this symbol."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The corpus database is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "CPC symbol, e.g. C11D or C11D7/261.",
            "schema": {
              "type": "string",
              "pattern": "^[A-HY][0-9A-Z/]{0,18}$"
            }
          },
          {
            "name": "childLimit",
            "in": "query",
            "required": false,
            "description": "How many child symbols to return.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "distributionLimit",
            "in": "query",
            "required": false,
            "description": "How many art units to list in `art_units.distribution`, largest first.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 500,
              "default": 20
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "lookup_cpc_code",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-intel/cpc/C11D?childLimit=5\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-intel/classification/{number}": {
      "get": {
        "operationId": "getPatentIntelClassificationNumber",
        "summary": "CPC classification for a publication or application number",
        "description": "The CPC assignment on one published application or granted patent, as the USPTO\nMaster Classification File records it.\n\nThe number is tried as a publication number first and an application number\nsecond, so a caller holding either can ask without saying which it has. The main\nsymbol is resolved against the scheme, so the answer carries a title and not\nonly a code.\n\n`inventive_cpcs` are the symbols covering what is claimed; `additional_cpcs`\ncover subject matter disclosed but not claimed. Callers building a search from\nthis usually want the inventive set alone.",
        "tags": [
          "Prosecution intelligence"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ publication_number, application_number, main_cpc, main_cpc_title, inventive_cpcs[], additional_cpcs[], ... }."
          },
          "400": {
            "description": "VALIDATION_ERROR: number is not 7-12 digits."
          },
          "404": {
            "description": "NOT_FOUND: No classification record for this number."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The corpus database is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "description": "Publication or US application number, digits only.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{7,12}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_patent_classification",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-intel/classification/20010000001\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-intel/art-unit/{artUnit}": {
      "get": {
        "operationId": "getPatentIntelArtUnitArtUnit",
        "summary": "Art-unit profile, statistics and examiner roster",
        "description": "One art unit: what it examines and how it behaves.\n\nAssembled from the four collections that describe a unit — the statistics, the\ndescriptive record, the generated enrichment and the examiner roster — so a\ncaller makes one request rather than four.\n\n`statistics` carries allowance and rejection behaviour, timing, interview\nreceptiveness, RCE tolerance and estimated cost, averaged across the unit's\nexaminers. Read `statistics.data_quality` before relying on any of it: it\nreports how many applications and office actions the averages rest on, and the\nvolume, recency and completeness scores behind them. Units with thin data carry\nthe same field names and much weaker numbers.\n\nThe roster's application numbers are not returned. One unit carries tens of\nthousands of them, which is most of a 209 KB document and no use in a profile.\nThe model-written `description` is not returned whole either: on 1725 it was\n9,887 characters, 56% of the response, and it filed \"bibs for hairdressers'\nrooms\" under a materials unit. `generated_context` says the same in a paragraph,\nso `description` is served as its opening and its length.\n\n`examiner_count` is the length of the roster served. Where the stored statistics\ncounted a different roster (1725 lists 28 examiners against a stored 27), the\nstored figure appears as `examiner_count_in_statistics` so the disagreement is\nvisible rather than resolved silently.",
        "tags": [
          "Prosecution intelligence"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ art_unit, technology_center, title, generated_context, description, keywords[], cpc_codes, examiners[], examiner_count, statistics }. cpc_codes summarises rather than lists: a total, counts by CPC section, and the 25 commonest subclasses. The raw list runs to 24,135 symbols for a single unit and two unrelated units share 57% of it, so it described the corpus more than the unit and made this response 383 KB. statistics is null for a unit that has a descriptive record but no computed statistics; inside it, claims_analysis.independentClaimsStats.avgIndependentClaims is null with a note where the stored value tracked the total claim count."
          },
          "400": {
            "description": "VALIDATION_ERROR: artUnit is not 3-4 digits."
          },
          "404": {
            "description": "NOT_FOUND: No collection holds this art unit."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The corpus database is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "artUnit",
            "in": "path",
            "required": true,
            "description": "USPTO art unit, e.g. 2872.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{3,4}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_art_unit_profile",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-intel/art-unit/2872\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-intel/examiners": {
      "get": {
        "operationId": "getPatentIntelExaminers",
        "summary": "Search examiners by name or art unit",
        "description": "Find examiners by surname or art unit.\n\nMatching is anchored: an exact surname, or a prefix of the full name in the\nUSPTO's surname-first form. An unanchored substring search would be a regex over\n10,308 documents averaging a megabyte each, which is a collection scan reading\n11 GB to answer a lookup. Callers wanting a specific examiner should pass the\nsurname and filter the result.",
        "tags": [
          "Prosecution intelligence"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ count, examiners[] }, each { examiner_id, full_name, art_unit, technology_center }. Empty when neither name nor artUnit is given."
          },
          "400": {
            "description": "VALIDATION_ERROR: A parameter failed validation."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The corpus database is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Surname, or a prefix of the surname-first full name.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 80
            }
          },
          {
            "name": "artUnit",
            "in": "query",
            "required": false,
            "description": "Restrict to one art unit.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{3,4}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum matches to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "find_examiner",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-intel/examiners?name=SMITH&artUnit=1609\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/patent-intel/examiner/{examiner}": {
      "get": {
        "operationId": "getPatentIntelExaminerExaminer",
        "summary": "Examiner statistics and behavioural profile",
        "description": "One examiner's statistics, by examiner id or by USPTO surname-first name.\n\nBoth forms are accepted because a caller reading an office action has the name\nand a caller following an art-unit roster has the id.\n\nThe response carries rejection analytics, portfolio counts, interview and RCE\nbehaviour, timing, cost analysis and percentile rankings against the unit and\nthe corps, plus `claim_interpretation` where a model-written read of how this\nexaminer construes claims exists — it does for 11 examiners, and is null for the\nrest rather than absent.\n\nSix sections are withheld by default and named in `sections_available`. Two are\ncold: `behavioral_patterns` and `rejection_analysis` hold per-application detail\nand are together 10.6 GB of the 11.1 GB collection, the largest single document\nbeing 14.2 MB. Four are hot but wide: `applications_with_interviews` and\n`applications_with_rcex` are per-application lists (hundreds of rows each),\n`by_sequence_length` and `document_code_breakdown` are histograms with hundreds of\nkeys. Together they were 83% of a default response. Each is replaced by its count\nand the `include` name that brings it back; `document_code_breakdown` keeps its ten\nlargest codes.\n\n`data_quality` reports what the numbers rest on, and says what was withheld or\nrelabelled. `populations` names the five denominators that appear in one response\n(all applications, analysed, with documents, with office actions, with a filing\ndate). `volume.tier` replaces an upstream score that was 5 for every examiner.\n`withheld[]` lists fields removed because their values were wrong and not\nrecoverable, with what was observed; `notes[]` lists sections served as null because\nthey were never computed (`percentile_rankings`, `claims_analysis` on every examiner\nsampled), and pairs of fields that disagree.",
        "tags": [
          "Prosecution intelligence"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ examiner_id, full_name, art_unit, technology_center, portfolio, rejection_analytics, interview_metrics, rcex_metrics, timeline_metrics, strategic_insights, data_quality, claim_interpretation, sections_available[], sections_included[], ... }. percentile_rankings and claims_analysis are null where never computed."
          },
          "400": {
            "description": "VALIDATION_ERROR: examiner is shorter than 2 characters or longer than 120."
          },
          "404": {
            "description": "NOT_FOUND: No examiner with this id or name."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The corpus database is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "examiner",
            "in": "path",
            "required": true,
            "description": "Examiner id, or USPTO surname-first full name.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 120
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated sections to add: `behavioral_patterns`, `rejection_analysis` (cold; megabytes), `applications_with_interviews`, `applications_with_rcex`, `by_sequence_length`, `document_code_breakdown` (hot; thousands of tokens). Unknown names are ignored.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_examiner_profile",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/patent-intel/examiner/SMITH%20ADAM%20M\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/guidance/mpep/{sectionId}": {
      "get": {
        "operationId": "getGuidanceMpepSectionId",
        "summary": "One MPEP section with its rules and required examiner actions",
        "description": "One MPEP section, as an examiner is required to apply it.\n\nBeyond the section text, each record carries `keyRules` and `examinerActions`: what the\nsection obliges, separated from the prose that obliges it. That is usually what a caller\nholding a rejection actually wants, since the argument is about whether the examiner did\nwhat the section required.\n\nSection ids carry dots and parentheses and the punctuation is part of the key, so\n`601.01(c)` is passed through exactly as written.",
        "tags": [
          "Examination guidance"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The section, with content, keyRules, examinerActions, legalReferences, technologyDomains and enrichedSummary."
          },
          "400": {
            "description": "VALIDATION_ERROR: sectionId is not a well-formed MPEP section id."
          },
          "404": {
            "description": "NOT_FOUND: No MPEP section carries that id."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The guidance dataset is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "description": "MPEP section id, e.g. `2106` or `601.01(c)`. Dots and parentheses are part of the id.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-Za-z.()\\-]{1,40}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_mpep_section",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/guidance/mpep/2106\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/guidance/epo/{sectionId}": {
      "get": {
        "operationId": "getGuidanceEpoSectionId",
        "summary": "One EPO Guidelines section with its rules and citations",
        "description": "One section of the EPO Guidelines for Examination.\n\nDeliberately the same shape as the MPEP route, because the flow that reaches it is the\nsame flow one step later: an applicant with a US rejection usually has a European family\nmember, and an agent that learned one lookup should not have to learn a second.\n\nThese records were merged from two source collections that each held half of each section,\nso a complete document carries both the generated half (`keyRules`, `examinerActions`) and\nthe source half (`citations`, `sourceUrl`, `parentSection`, `childSections`).",
        "tags": [
          "Examination guidance"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The section, with content, keyRules, examinerActions, citations, parentSection, childSections and sourceUrl."
          },
          "400": {
            "description": "VALIDATION_ERROR: sectionId is not a well-formed EPO section id."
          },
          "404": {
            "description": "NOT_FOUND: No EPO section carries that id."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The guidance dataset is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "sectionId",
            "in": "path",
            "required": true,
            "description": "EPO Guidelines section id, e.g. `c-iv-2` or `g-ii-3.3`.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-Za-z.\\-]{1,40}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_epo_guideline",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/guidance/epo/g-ii-3.3\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/guidance/form-paragraph/{code}": {
      "get": {
        "operationId": "getGuidanceFormParagraphCode",
        "summary": "One office-action form paragraph, with its statute and MPEP section",
        "description": "The literal paragraph an examiner pasted into an office action.\n\nThis is the sharpest thing in the dataset. A caller holding a rejection has a paragraph\nnumber and nothing else, and this turns it into the statute behind it, the MPEP section it\nrests on, the rejection type it belongs to, and the notes the examiner was given about\nwhen to use it. `brackets` lists the placeholders the examiner had to fill, which is often\nwhere the substance of a rejection actually sits.\n\n`isAIA` and `preAIAEquivalent` matter for anything filed near the changeover: citing the\nwrong one is citing law that does not apply to the application.\n\nThe paragraph symbol is stripped, so a code copied straight out of a PDF as `¶ 6.11`\nresolves rather than returning a 404 that reads as \"no such paragraph\".",
        "tags": [
          "Examination guidance"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The paragraph: title (its heading), template (the text as pasted, placeholders as [1], [2]), brackets, statute, statuteShort, rejectionType, category, isAIA, preAIAEquivalent, mpepSection, examinerNotes, mustFollow (paragraphs that have to appear before this one) and relatedParagraphs. truncated_at_source: true means the stored text ends before the paragraph does; quote it as partial. statute and rejectionType are each null on a majority of rows, and on different rows."
          },
          "400": {
            "description": "VALIDATION_ERROR: code is not a well-formed form paragraph code."
          },
          "404": {
            "description": "NOT_FOUND: No form paragraph carries that code."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The guidance dataset is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Form paragraph code, e.g. `6.11` or `7.37.01`. A leading `¶` is accepted and ignored.",
            "schema": {
              "type": "string",
              "pattern": "^[¶\\s0-9A-Za-z.\\-]{1,24}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_form_paragraph",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/guidance/form-paragraph/7.34.10\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/guidance/form-paragraphs": {
      "get": {
        "operationId": "getGuidanceFormParagraphs",
        "summary": "Form paragraphs filtered by rejection type or MPEP section",
        "description": "Every form paragraph an examiner could have reached for.\n\nGiven a rejection type, this is the set the examiner chose from, which is what an agent\nneeds to tell \"they picked the narrow one\" from \"this was the only option\". Filtering by\n`mpepSection` answers the reverse: which paragraphs rest on a section you are already\narguing about.\n\nAt least one filter is required. An unfiltered call would page through everything to no\npurpose, so the caller has to say which axis they are on rather than receive the whole\ntable by default.\n\n`rejectionType` takes either of the two vocabularies the corpus holds. The stored label\nis a word (`obviousness`, `indefiniteness`, `means_plus_function`) and the stored statute\nis a citation (`35 U.S.C. 103(a)`, `37 CFR 1.52`); each is null on a majority of rows,\nand on different rows. A query on either is expanded to both, so `112(b)` reaches the\nparagraphs labelled indefiniteness as well as the ones citing the statute, and\n`indefiniteness` reaches the same set. `filter.matched` shows the expansion.",
        "tags": [
          "Examination guidance"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ results[], count, filter }. filter echoes what was applied and filter.matched what rejectionType expanded to; an unrecognised value returns zero with filter.note listing the vocabulary."
          },
          "400": {
            "description": "VALIDATION_ERROR: Neither filter was supplied, or one was malformed."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The guidance dataset is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "rejectionType",
            "in": "query",
            "required": false,
            "description": "A statute (`102`, `103`, `112(b)`, `101`, `121`, or a CFR rule such as `1.52`) or a label (`obviousness`, `anticipation`, `indefiniteness`, `enablement`, `written_description`, `means_plus_function`, `abstract_idea`, `double_patenting`, `restriction`, `allowance`, `drawing_objection`). Required unless `mpepSection` is given.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-Za-z.()_ \\-]{1,40}$"
            }
          },
          {
            "name": "mpepSection",
            "in": "query",
            "required": false,
            "description": "MPEP section the paragraph rests on, e.g. `706`, `707`, `2175`. Required unless `rejectionType` is given.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-Za-z.()\\-]{1,40}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many paragraphs to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "find_form_paragraphs",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/guidance/form-paragraphs?rejectionType=112(b)&limit=5\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/guidance/graph/{nodeId}": {
      "get": {
        "operationId": "getGuidanceGraphNodeId",
        "summary": "One-hop cross-references into and out of a guidance section",
        "description": "What a guidance section points at, and what points at it.\n\nOne hop, both directions, because they answer different questions. Forward is what else\nthis rule depends on. Backward is what breaks if you misread it, and it is the direction\nthat is hard to get any other way.\n\nWhich graph is read off the node id rather than passed separately: ids are\n`mpep-601.01(c)` and `epo-c-ix-3`, so the prefix routes the query. An id with neither\nprefix is a 404 rather than a guess, because answering an EPO question with US law is\nworse than answering nothing.\n\nA hub section is cited by hundreds of others, so the fan-out is capped and `truncated`\nsays which direction hit the cap. A caller seeing exactly `limit` edges cannot otherwise\ntell a hub from a coincidence.",
        "tags": [
          "Examination guidance"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ node, corpus, outgoing[], incoming[], truncated, limit }. Each edge is { source, target, weight, type, bidirectional }."
          },
          "400": {
            "description": "VALIDATION_ERROR: nodeId carries no recognised prefix, or limit is outside 1-200."
          },
          "404": {
            "description": "NOT_FOUND: No graph node carries that id."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The guidance dataset is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "nodeId",
            "in": "path",
            "required": true,
            "description": "Graph node id. The prefix selects the corpus: `mpep-2106`, `epo-c-ix-3`.",
            "schema": {
              "type": "string",
              "pattern": "^(mpep|epo)-[0-9A-Za-z.()\\-]{1,40}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum edges per direction.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_guidance_graph",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/guidance/graph/mpep-2106?limit=10\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    },
    "/guidance/design-code/{code}": {
      "get": {
        "operationId": "getGuidanceDesignCodeCode",
        "summary": "One USPTO design search code",
        "description": "One USPTO design search code.\n\nA classification scheme rather than guidance. It sits in this namespace because it\ntravelled with the same passover, not because it belongs beside the MPEP, and it is\ndocumented that way rather than filed somewhere tidier that would imply a relationship\nthese records do not have.",
        "tags": [
          "Examination guidance"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The code, with descriptions[] (every phrasing the USPTO files it under), major and sub."
          },
          "400": {
            "description": "VALIDATION_ERROR: code is not a design search code."
          },
          "404": {
            "description": "NOT_FOUND: No design code carries that value."
          },
          "503": {
            "description": "CORPUS_UNAVAILABLE: The guidance dataset is not configured or not reachable."
          }
        },
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Design search code, e.g. `03.19.17`.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{2}(\\.[0-9]{2}){0,3}$"
            }
          }
        ],
        "x-cost": "metered",
        "x-cache-control": "private, max-age=86400",
        "x-mcp-tool": "get_design_code",
        "x-code-samples": [
          {
            "lang": "shell",
            "source": "curl -sS \"$BASE/guidance/design-code/03.19.17\" \\\n  -H \"Authorization: Bearer $ANTECEDINT_API_KEY\""
          }
        ]
      }
    }
  }
}
