---
title: "Search the text of state session laws"
method: POST
path: "/api/v1/us/session-laws/search"
tags: ["US Session Laws"]
---

# Search the text of state session laws

`POST /api/v1/us/session-laws/search`

## Request

- Base URL: `https://api.vaquill.ai`
- URL: `https://api.vaquill.ai/api/v1/us/session-laws/search`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/vaquill/apis/vaquill-developer-api/docs/api/v1/us/session-laws/search/post.ts> (a function with typed inputs; its response is checked against the schema)

Find state session laws by what they say. Send a question in plain words, words from a title, a quoted
phrase, a citation (`63-3004`), a code section or a bill number (`S.F.No. 1552`). You get the laws, best
first, each with the passage of its text that matched and a snippet around your words.

**Cost**: 4 credits per page, each page a new search. An empty result from a search narrowed by
`jurisdiction`, `session`, `series`, `instrumentType` or the approval dates is refunded and carries a `note`
naming the filters; an empty result with no filter is an answer ("nothing matches"), is charged, and carries
a `note`. A request that fails validation (422) is not charged, including a `query` with nothing to search
for (`??`, `the of`); a failure on our side (500, 503) is refunded. To read a law you found, use
`GET /us/session-laws/{sessionLawId}` (record) or its `/body` (text), which are priced on their own.

**What is searched.** The enacted text: the law as it reads once the act takes effect. Language an act
strikes out is not searched and does not appear in a snippet, so a repealed word cannot match as if it were
law. Only laws we serve with text are searched (see `GET /us/statutes/coverage`, `sessionLaws`, for what
each state holds). Laws whose text we withhold are returned only with `includeUnverified`, which also changes
how they are matched (see that parameter). `session` limits every kind of match to that session.

**How it ranks.** Keyword relevance (BM25), not meaning. Words are matched as written and stemmed
(`licensing` finds `licensed`), so a question has to share vocabulary with the act:
`teeth cleaning professional` does not find an act that says `dental hygienist`. A law matched by its
title AND its text, or by an exact part of your query, ranks above one matched a single way, whatever
the age of either. Among laws matched alike, the newer act comes first and an act that amends few code
sections comes before an omnibus one. Quote a phrase to require it verbatim. The act's own citation
(`Laws 2025, ch. 127`) is searched too.

**What a result says about itself.** `matchType` is `exact` when a quoted phrase, citation, code
section or bill number you typed matched, otherwise `text`; `matchedBy` lists every way. Neither says
the law is the right one: a question with no true answer still returns its closest words, and there is no
"no good match" flag because keyword scores do not separate the two cases. Read the snippet.

**Paging.** `limit` is 1 to 25. `offset` plus `limit` reaches at most the 60 best matches; narrow with
`jurisdiction`, `session`, `series`, `instrumentType` or the approval dates rather than paging deeper.

```bash
curl -X POST "https://api.vaquill.ai/api/v1/us/session-laws/search" \
  -H "Authorization: Bearer vq_key_..." \
  -H "Content-Type: application/json" \
  -d '{"query": "can the sheriff pay with a debit card", "jurisdiction": "al", "limit": 5}'
```

```javascript
const res = await fetch("https://api.vaquill.ai/api/v1/us/session-laws/search", {
  method: "POST",
  headers: { Authorization: "Bearer vq_key_...", "Content-Type": "application/json" },
  body: JSON.stringify({ query: '"credit card or debit card"', jurisdiction: "al", limit: 5 }),
});
const { results } = await res.json();
```

**Related.** `GET /us/session-laws/list` filters one state's laws by session, series and date;
`POST /us/session-laws/batch` reads up to 50 laws by id; `GET /us/statutes/section/{actId}/enactments`
lists the acts a publisher's own tables tie to a code section.

## Headers

- `Idempotency-Key` string, nullable: Optional. 16 to 255 visible ASCII characters (a UUIDv4 works). Repeating the same request with the same key replays the original 2xx response for 24 hours without charging again. The same key on a different request is rejected. With the account's `cacheQueryDerivedData` retention setting off, no body is stored and a retry answers 409 `idempotency_replay_unavailable`, still without a second charge.

## Request body

- SessionLawSearchRequest: Body of `POST /us/session-laws/search`.
  - `query` string, required: What to look for: a question in plain words (`can a sheriff pay with a debit card`), words from a title, a quoted phrase (`"credit card or debit card"`), a citation (`63-3004`), a code section or a bill number (`S.F.No. 1552`). Quoted parts must appear verbatim; everything else is matched on its words, stemmed, so `licensing` finds `licensed`. Words are matched as the legislature wrote them: `teeth cleaning professional` does not find an act that says `dental hygienist`. 2 to 300 characters.
  - `jurisdiction` 'al' | 'ak' | 'as' | 'az' | 'ar' | 'ca' | 'co' | 'ct' | 'de' | 'dc' | 'fl' | 'ga' | 'gu' | 'hi' | 'id' | 'il' | 'in' | 'ia' | 'ks' | 'ky' | 'la' | 'me' | 'md' | 'ma' | 'mi' | 'mn' | 'ms' | 'mo' | 'mt' | 'ne' | 'nv' | 'nh' | 'nj' | 'nm' | 'ny' | 'nc' | 'nd' | 'mp' | 'oh' | 'ok' | 'or' | 'pa' | 'pr' | 'ri' | 'sc' | 'sd' | 'tn' | 'tx' | 'ut' | 'vt' | 'vi' | 'va' | 'wa' | 'wv' | 'wi' | 'wy', nullable: Limit to one jurisdiction: the 56 jurisdictions (the 50 states, `dc` and the territories `pr`, `gu`, `vi`, `as`, `mp`). Two-letter code, case-insensitive. Omit to search every jurisdiction we hold. `federal` and an unknown code are a 422, never charged.
  - `session` string, nullable: Limit to one session, by the code `session.code` reports (`2025R`, `2025S1`). Case-sensitive. Matches the act's own session on every kind of match: a law of another session is never returned, including one found through the words of its text (the session is re-checked on each law before it is ranked, so a narrow session can leave fewer than `limit` laws on a page).
  - `series` string[], nullable: Limit to these numbering series, one or more (they combine with OR). One of `general`, `public`, `public_act`, `special_act`, `private_and_special`, `act`, `acts`, `law`, `local`, `municipal`, `appropriation`, `resolve`, `resolves`, `resolution`, `resolution_chapter`, `joint_resolution`, `concurrent_resolution`, `memorial`, `initiated_bill`, `constitutional_amendment`, `private`, `constitutional_resolution`, `competing_measure_resolution`, `resolution_act`. Any other value is a 422, never charged.
  - `instrumentType` string[], nullable: Limit to these kinds of measure, one or more (OR). One of `act`, `joint_resolution`, `concurrent_resolution`, `resolution`, `resolve`, `memorial`, `other`. Any other value is a 422, never charged.
  - `approvedFrom` string, date, nullable: Only laws approved on or after this date, `YYYY-MM-DD`, inclusive; a real calendar date (`2025-02-30` is a 422). A law with no printed approval date never matches.
  - `approvedTo` string, date, nullable: Only laws approved on or before this date, `YYYY-MM-DD`, inclusive. An end before `approvedFrom` is a 422, never charged.
  - `limit` integer: Laws per page, 1 to 25. A page costs the same however many laws it holds, so a larger `limit` is cheaper per law.
  - `offset` integer: Laws to skip, for the next page: `offset` plus `limit` can reach at most the 60 best matches. Each page is a new search and is charged. Deeper than 60 is a 422, never charged: narrow the search with a filter instead.
  - `includeUnverified` boolean: Also search laws we hold but do not serve (`holdingState: quarantined`). Default `false`: such laws are never returned and nothing else changes. With `true` a held-back law whose reason we can label may appear, and every such result carries `holdingState`, `textReliability` (`unverified` or `known_issue`) and `reasonCode`; its text MAY show deleted or vetoed law as if enacted. Served laws are returned as usual, without these fields. A law whose licence bars use, or whose text failed our own validation, is never returned. A held-back law is matched by its title, code section, its OWN citation (`Laws 2025, ch. 127`) or bill number, not by the words of its text and not by a code citation that appears inside it. Priced as any search page.

## Response `200`

The laws that matched, best first. An empty `results` is a charged answer.

- SessionLawSearchResponse: Response for `POST /us/session-laws/search`.
  - `query` string, required: The query as read, whitespace collapsed.
  - `parsed` SessionLawSearchParsed, required: How the query was read, so a caller can see why a result matched.
    - `text` string, required: The free words, quoted parts removed.
    - `phrases` string[]: Quoted phrases, matched verbatim.
    - `citationTokens` string[]: Citation-shaped tokens found in the query (`63-3004`), matched exactly.
    - `billNumbers` string[]: Bill numbers found in the query (`S.F.No. 1552`), matched exactly.
    - `codeSections` string[]: Code section numbers found in the query after a word that says they are one (`Civil Code 1950.5`, `section 1950.5`, `§ 1950.5`), matched exactly against the code sections each act's own table lists. A bare number is never read as a section.
  - `results` SessionLawSearchResult[]: The laws, best first. Empty when nothing matched: a search narrowed by a filter is then refunded, an unfiltered one is an answer and is charged. Either way `note` says which.
    - `sessionLawId` string, required: The law's permanent id. Read the whole law at `GET /us/session-laws/{sessionLawId}`.
    - `jurisdiction` string, required: Lowercase two-letter code of the state, `dc` or `pr`.
    - `session` string, required: The session's code, as `session.code` reports it.
    - `series` string, required: The numbering series the law sits in.
    - `instrumentType` string, required: The kind of measure: `act`, `joint_resolution` and so on.
    - `citation` string, required: The law's canonical citation, as the registry holds it.
    - `title` string, nullable: The act's title as the publisher prints it; null where the publisher printed none.
    - `approvedDate` string, nullable: The date the governor approved it, `YYYY-MM-DD`, where the publisher printed one; else null.
    - `enactmentOutcome` string, required: How it became law: `signed`, `became_law_without_signature`, `veto_overridden` and so on.
    - `billNumbers` string[]: The bill number(s) this act passed as, as the publisher printed them (`AB 2801`). A bill number is reused every session, so read it with `session` and `citation`. Empty when the publisher printed none.
    - `amendedSections` string[]: The code section numbers this act's own table lists (`1950.5`), at most 25, without the code's name: the title names the code (`... of the Civil Code`). Use it to judge whether this act touches a section you care about. Empty when the act lists none.
    - `matchType` 'exact' | 'text', required: `exact` when a quoted phrase, a citation, a code section or a bill number you typed matched this law; `text` when it matched on words alone. It says HOW it matched and makes no claim that this is the right law: a question with no true answer still returns its closest words.
    - `matchedBy` string[], required: Every way this law matched, strongest context first. `title`: the words are in the act's title. `text`: they are in its enacted text. `phrase`: your quoted phrase appears verbatim. `citation`: a citation you typed (`63-3004`) appears in the text, or the query is the act's own citation (`Laws 2025, ch. 127`). `codeSection`: the act's own table lists that code section. `billNumber`: the act's bill number is the one you typed. A law matched by several ranks above one matched by a single way.
    - `passage` SessionLawSearchPassage: The passage of the law that matched best, with a cut of its text.
      - `seq` integer, required: Position of this passage within the law, from 0.
      - `ownStart` integer, required: Character offset in the law's stored text where this passage begins. Pass it as `startChar` to `GET /us/session-laws/{sessionLawId}/body` to read the law from here.
      - `ownEnd` integer, required: Character offset where this passage ends, in the same scale as `ownStart`.
      - `pageStart` integer, nullable: The printed page the passage starts on, 1-based, where the publisher's pages are known; else null.
      - `heading` string, nullable: The section heading the passage sits under (`Section 3.`), where one was found; else null.
      - `snippet` string, required: About 280 characters of the ENACTED text around your words, plain text, with `…` where it was cut. Language the act strikes out is never shown here.
      - `highlights` array[]: `[start, end]` character offsets INSIDE `snippet` (end exclusive) of each word that matched, so you can draw the highlight yourself. The snippet carries no markup.
        - integer[]
      - `bodyHref` string, required: Where to read the text from this passage: the law's `/body` with `startChar` set.
    - `holdingState` 'quarantined', nullable: `quarantined`: we hold this law but do not serve it. Present only on a result found with `includeUnverified=true`; omitted for a served law.
    - `textReliability` 'unverified' | 'known_issue', nullable: How far to trust this law's text, present with `holdingState`. `unverified`: a check has not been completed. `known_issue`: we have evidence the text shows deleted or vetoed law as if enacted. Branch on `reasonCode`.
    - `reasonCode` string, nullable: Why the law is held back, a stable token: `markup_unverified`, `strike_unchecked`, `struck_text_shown_as_current`, `struck_text_merged`, `veto_unchecked` or `licence_unconfirmed`. Present with `holdingState`.
    - `href` string, required: The law's record: `GET /us/session-laws/{sessionLawId}`.
  - `note` string, nullable: Set when `results` is empty: says why and whether the call was charged.
  - `count` integer: Laws on this page.
  - `offset` integer: The `offset` this page was read from.
  - `limit` integer: The `limit` this page was read with.
  - `hasMore` boolean: True when more laws follow: call again with `offset` raised by `limit` (within the 60 best).
  - `creditsConsumed` integer: Credits actually charged for this call, never the list price. 0 when the call was refunded.
  - `processingTimeMs` number: Server-side time for this request in milliseconds, excluding network transit. Not billed on.

## Other responses

- `401`: Missing, invalid or revoked API key. Not charged.
- `402`: Insufficient credits for this call. Nothing was done and nothing was charged. Read your balance, free, at `GET /credits/balance`.
- `403`: The API key lacks the `research:read` scope. Not charged.
- `409`: Idempotency conflict. `idempotency_in_progress`: the original request with this key is still running (retry after `Retry-After`). `idempotency_replay_unavailable`: it completed but its response was too large to replay; nothing is charged again, so check the credit balance instead of retrying.
- `422`: A parameter failed validation: `query` shorter than 2 characters, with control characters or with nothing to search for (`??`, `the of`), `jurisdiction` not a state, an unknown `series` or `instrumentType`, a date that is not a real calendar date, `approvedTo` before `approvedFrom`, or `offset` past the 60 best matches. Never charged. `errors[].loc` names the parameter exactly as you send it. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different request.
- `429`: Rate limit exceeded for your key. Not charged. Wait `Retry-After` seconds.
- `500`: An unexpected failure on our side. The charge is refunded.
- `503`: The session-laws database is temporarily unavailable. The charge is refunded; retry.

## Changes

- **2026-11-07** `340c0d24d0b6`: 74 info
  - added the optional property `candidateCount` to the response with the `401` status
  - added the optional property `candidateCount` to the response with the `402` status
  - added the optional property `candidateCount` to the response with the `403` status
  - added the optional property `candidateCount` to the response with the `409` status
  - …70 more

[Operation history](https://skmtc.dev/vaquill/apis/vaquill-developer-api/changes/api/v1/us/session-laws/search/post.md)

---

[API](https://skmtc.dev/vaquill/apis/vaquill-developer-api.md) - [All operations](https://skmtc.dev/vaquill/apis/vaquill-developer-api/llms.txt) - [OpenAPI document](https://skmtc.dev/vaquill/apis/vaquill-developer-api/revisions/340c0d24d0b6?raw)
