curl --request GET \
--url https://api.vaquill.ai/api/v1/us/session-laws/list \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.vaquill.ai/api/v1/us/session-laws/list"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.vaquill.ai/api/v1/us/session-laws/list', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.vaquill.ai/api/v1/us/session-laws/list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}List a state's session laws
List the session laws of one state: every act its legislature enacted, filtered
by session, approval date or effective date. This is how you ask “what became law
in Texas this session”, “what was signed in April” or “what takes effect on
September 1, 2025”, and how you turn the answer into sessionLawIds for the get
and body endpoints. A session law is the act as enacted, before it is folded into
a code; v1 serves STATE acts.
Cost: 1 credit per page of up to 200 rows (default 50), however many rows the page holds. An empty page is an answer and is charged when it means nothing matches your filters. A request that fails validation (422) is never charged; a failure on our side (500, 503) is refunded.
Empty pages. A page with no rows carries coverage, what we hold of the session you
named (or of the state, when you named none), so you can tell “nothing matches” from “we
have not collected this”. coverage.status is complete (we hold every law the publisher
lists for it: the empty page is a true negative, and it is charged), partial (the
collection stopped short, so the law you want may exist) or not_held (we hold nothing of
it). On partial and not_held the page is NOT charged and creditsConsumed is 0: we
do not bill an empty answer that may be our gap. The one exception is a request that
names only the state, where some sessions we hold are complete and some partial: the page
is charged, since for the complete ones it is a true negative, and coverage.partialSessions
names the sessions to doubt (send one as session to ask about it alone). If coverage
cannot be read the page is charged as before and coverage is omitted.
Filters. jurisdiction is required and scopes everything; the rest narrow it
and combine with AND. A value that is malformed is a 422, and so is a range whose end is before its
start (approvedTo before approvedFrom, effectiveTo before effectiveFrom). A value that is well
formed and matches nothing (an unknown session, a date range with no laws) is a
200 with results: []. Dates are YYYY-MM-DD.
- Everything from one session:
session(2025R,2025S1). - Approved in a window:
approvedFrom/approvedTo. - Taking effect in a window:
effectiveFrom/effectiveTo. For one day send the same date to both. This matches laws whose first-to-last effective dates overlap your window, so readeffectiveFirstandeffectiveLaston each row andeffectiveDateson the law. A law with no held effective date (not every state prints them) never matches. - Narrow by
series,instrumentType,hasTextortextStatus. - What became law and what did not:
isLaw(falsefor the vetoed and pocket-vetoed measures; a measure whose outcome does not settle it has a nullisLawand matches neither value) andenactmentOutcome, repeated for several outcomes (enactmentOutcome=vetoed&enactmentOutcome=pocket_veto). - A sync job:
updatedSincereturns the laws whose record changed on or after a date (midnight UTC, byupdatedAt), so a job that stores its last run pulls only what is new or corrected. - Find by words:
q, a case-insensitive substring of thetitleor the bill number as the publisher prints it (3 to 100 characters). It is a substring match inside one state, not a search engine. - A derived approval date (
approvedDateDerived: true) is not matched byapprovedFromandapprovedTo, which read the registry’s own approval date.
Paging. Rows come in session.code order, then sessionLawId, both as TEXT in the
database’s collation, not in chapter order: ..._C10 sorts before ..._C2, and the id’s
series and numbering-year segments sort before the chapter. Sort client-side by number
if you need chapter order. The order is the same on every page and for every filter,
so a cursor never skips or repeats a row. Send
nextCursor back as cursor, with the same filters, while hasMore is true (a
cursor sent with different filters is a 422, cursor_query_mismatch: restart
without it). The cursor is a keyset (it names the last row you saw), so laws added between your
calls never repeat or skip a row you have passed. count is the size of this
page, never a total.
Text. Rows include laws whose text we do not hold (textStatus: not_held) or
hold and withhold (withheld, with withheldReason): the row is still a real law.
Pass hasText=true for just the laws /{sessionLawId}/body will serve. A row is
the summary only: the full dates, sources and code sections are on
GET /us/session-laws/{sessionLawId}. Each row carries charCount and
estimatedBodyPages, so you can price reading a law before you open it.
citableAs is the law cited the way a lawyer writes it (Minn. Laws 2025, ch. 1), and
licenseNote says what its licence class permits.
Cost of reading a law. Reading a whole law is estimatedBodyPages times the price
of one body page (6 credits). Both are on the record: charCount is its length
in characters and estimatedBodyPages is that divided by the page size (about
30,000 characters), rounded up, and null unless the text is served. Most acts
take a page or two. The longest we hold, Ohio’s 2025 budget act (HB 96, 9,571,132
characters), takes about 320 pages, 1,920 credits to read in
full, so check estimatedBodyPages before you page through an omnibus act. Each body
response then carries pagePrice (what that page cost) and estimatedRemainingCredits
(the pages still to come times the price) so you can stop whenever you have enough.
Example
curl "https://api.vaquill.ai/api/v1/us/session-laws/list?jurisdiction=tx&effectiveFrom=2025-09-01&effectiveTo=2025-09-01&limit=25" \
-H "Authorization: Bearer $VAQUILL_API_KEY"
# only what changed since your last sync, and only measures that did not become law
curl "https://api.vaquill.ai/api/v1/us/session-laws/list?jurisdiction=mn&updatedSince=2026-10-01" \
-H "Authorization: Bearer $VAQUILL_API_KEY"
curl "https://api.vaquill.ai/api/v1/us/session-laws/list?jurisdiction=mn&isLaw=false&enactmentOutcome=vetoed&enactmentOutcome=pocket_veto" \
-H "Authorization: Bearer $VAQUILL_API_KEY"
# find by words in the title or the printed bill number
curl "https://api.vaquill.ai/api/v1/us/session-laws/list?jurisdiction=mn&q=court%20fees" -H "Authorization: Bearer $VAQUILL_API_KEY"
const url = new URL("https://api.vaquill.ai/api/v1/us/session-laws/list");
url.search = new URLSearchParams({ jurisdiction: "mn", session: "2025R" });
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.VAQUILL_API_KEY}` },
});
const { results, nextCursor, hasMore } = await res.json();
Related endpoints
GET /us/statutes/resolveanswers a citation that names a state session law rather than a code section with asessionLawblock (matched, orambiguouswith every candidate), pointing here. It never changesresolvedorsection.GET /us/statutes/coveragelists, per jurisdiction undersessionLaws, which sessions we hold session laws for and whether each iscompleteorpartial. Read it first: a law missing from apartialsession is not evidence that it does not exist.- Federal session laws (the Statutes at Large,
SAL_ids) are not served here. Read them withGET /us/statutes/section/{actId}.
Nulls. Every field of a response is always present: one with no value is null
(or [] for a list). The examples on this page leave the nulls out for brevity. The
exceptions are blocks that exist only when they apply and are OMITTED otherwise, never
null: publisher, provenance and session.coverageStatus on a law, and coverage on a
list page that has rows.
Rate limits. Calls count against your key’s per-minute budget like every Data
API route; a 429 carries Retry-After.
curl --request GET \
--url https://api.vaquill.ai/api/v1/us/session-laws/list \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.vaquill.ai/api/v1/us/session-laws/list"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.vaquill.ai/api/v1/us/session-laws/list', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.vaquill.ai/api/v1/us/session-laws/list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}Authorizations
API key issued from the developer dashboard. Pass as Authorization: Bearer vq_key_... (preferred).
Query Parameters
Two-letter code of the state whose session laws to list: the 50 states, dc and pr. Case-insensitive (MN works). Required: the list is scoped to one jurisdiction at a time. federal and any unknown code is a 422; federal session laws are on /us/statutes/section. A jurisdiction we hold no session laws for yet (Indiana and Tennessee at launch) returns an empty page, which is charged: see sessionLaws on /us/statutes/coverage for what is held.
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 "mn"
Only laws enacted in this session. Use the code exactly as session.code reports it: 2025R is the 2025 regular session, 2025S1 the first special session of 2025. Case-sensitive, so 2025r matches nothing. A code that matches no session returns an empty page (charged), not an error; characters other than letters, digits, ., _ and - are a 422. Omit to list every session.
1 - 32^[A-Za-z0-9._-]+$"2025R"
Only laws in one of these numbering series. Repeat the parameter for several (series=resolve&series=resolves); they combine with OR. general is the ordinary chapter sequence of most states; others number acts, resolutions or local laws apart. 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, as a result's series reports it. Not every jurisdiction uses every series: sessionLaws on /us/statutes/coverage lists each one's. Any other value is a 422, never charged; a valid series nothing matches returns an empty page (charged). Omit for every series.
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 ["general"]
Only these kinds of measure. Repeat the parameter for several (instrumentType=joint_resolution&instrumentType=concurrent_resolution); they combine with OR. One of act, joint_resolution, concurrent_resolution, resolution, resolve, memorial, other (act is an ordinary law), as instrumentType reports it. Any other value is a 422, never charged; a valid kind nothing matches returns an empty page (charged). Omit for every kind.
act, joint_resolution, concurrent_resolution, resolution, resolve, memorial, other ["joint_resolution", "concurrent_resolution"]
Only laws approved (signed) on or after this date. Format YYYY-MM-DD, inclusive; it must be a real calendar date (2025-02-30 is a 422). A law with no printed approval date never matches, and neither does one whose approvedDate we derived (approvedDateDerived: true): this filter reads the registry's own approval date, so South Dakota's derived dates are not found by it. Pair with approvedTo for a range; an end before the start is a 422 (never charged).
^\d{4}-\d{2}-\d{2}$"2025-04-01"
Only laws approved (signed) on or before this date. Format YYYY-MM-DD, inclusive; it must be a real calendar date (2025-02-30 is a 422). Like approvedFrom, it does not match a derived approvedDate.
^\d{4}-\d{2}-\d{2}$"2025-05-31"
Only laws with an effective date on or after this date. Format YYYY-MM-DD, inclusive; it must be a real calendar date (2025-02-30 is a 422). A law matches when the span from its first to its last effective date (effectiveFirst to effectiveLast) overlaps your window, so one whose dates straddle your day is returned even though none falls exactly on it: read effectiveDates on the law for the dates themselves. Only laws with a held effective date can match, and not every state's publisher prints them (Texas, Washington and Iowa do; Minnesota does not yet): a law with no effective date is never returned by this filter, though it may well be in force. To get everything taking effect on one day, send the same date to both effectiveFrom and effectiveTo; for example jurisdiction=tx, effectiveFrom=2025-09-01, effectiveTo=2025-09-01 lists the Texas acts effective September 1, 2025.
^\d{4}-\d{2}-\d{2}$"2025-09-01"
Only laws with an effective date on or before this date. Format YYYY-MM-DD, inclusive; it must be a real calendar date (2025-02-30 is a 422). Overlap rule as for effectiveFrom. An end before effectiveFrom is a 422 (never charged).
^\d{4}-\d{2}-\d{2}$"2025-09-30"
true: only laws whose text is served (textStatus: held), the ones /{sessionLawId}/body will return. false: only laws whose text is not served (withheld, not_held or pending). Omit for both. Combined with textStatus by AND, so hasText=true&textStatus=withheld is always empty. Anything but a boolean is a 422.
true
Only laws with this text status. One of held, withheld, not_held, pending: held is served, withheld is held but kept back for a measured defect, not_held is known to exist without text, pending is collected and not yet verified. Any other value is a 422. Omit for every status.
held, withheld, not_held, pending "held"
true: only measures that are law. false: only numbered measures that are NOT law (a vetoed bill, a pocket veto), the ones isLaw: false marks. Omit for both. A measure whose outcome does not settle it (not_presented, unknown) has a null isLaw, so it matches neither true nor false: use enactmentOutcome for those. Anything but a boolean is a 422.
false
Only laws with one of these outcomes. Repeat the parameter for several (enactmentOutcome=vetoed&enactmentOutcome=pocket_veto); they combine with OR. One of signed, became_law_without_signature, veto_overridden, line_item_veto, vetoed, pocket_veto, not_presented, approved_by_voters, unknown, as enactmentOutcome reports it. Any other value is a 422, never charged; a valid outcome nothing matches returns an empty page (charged). The order you send them in does not matter, and a cursor stays valid for the same set.
signed, became_law_without_signature, veto_overridden, line_item_veto, vetoed, pocket_veto, not_presented, approved_by_voters, unknown ["vetoed", "pocket_veto"]
Only laws whose record changed on or after this date, midnight UTC, by updatedAt: the filter for a sync job that pulls only what is new or corrected since its last run. Format YYYY-MM-DD, inclusive; it must be a real calendar date (2025-02-30 is a 422). A day with no change returns an empty page (charged), not an error.
^\d{4}-\d{2}-\d{2}$"2026-10-01"
Only laws whose title or printed billNumber contains this text, case-insensitively (grain buyers finds An act relating to agriculture; modifying financial reporting requirements for grain buyers ...). A plain substring, not a search engine: no stemming, no ranking, and % and _ mean themselves. The bill number is matched as the publisher prints it (S.F.No. 1552, not SF 1552): to find a law by citation, use GET /us/session-laws/{sessionLawId}. 3 to 100 characters after trimming and without control characters, else a 422 (never charged); text that matches nothing returns an empty page (charged). Combines with the other filters by AND within the required jurisdiction.
3 - 100"grain buyers"
Where to resume: the previous page's nextCursor, unchanged. Send it with the same filters that produced it (limit may change). It is opaque, signed and tied to those filters, and is only ever issued by this API: a value we did not issue is a 422 with reason: invalid_cursor, one from an older cursor format is cursor_expired, and one sent with different filters is cursor_query_mismatch (restart without cursor). Never an empty page. Omit for the first page.
600"eyJ2IjoyLCJqIjoibW4iLCJzIjoiMjAyNVIiLCJpIjoiU1NMX01OXzIwMjVSX0dfWTIwMjVfQzEwIiwicSI6IjYxMjk2NDEzOTE2OTJmOTYifQ.86d0NXnVAJD7j_3d9zoWPQ"
Rows per page, 1 to 200; default 50. A page costs the same however many rows it holds, so a larger limit is cheaper per law. Outside the range is a 422.
1 <= x <= 20025
Response
A page of the state's session laws. hasMore and nextCursor say whether another follows; an empty results is a charged answer, unless coverage says the session or state is not fully collected.
Response for GET /us/session-laws/list.
Server-side time for this request in milliseconds, excluding network transit. Not billed on.
747.5
Credits actually charged for this call, never the list price. It includes the /us/statutes/resolve fee when a citation was sent where an id goes. 0 when the call was not charged or was refunded (a body that answers available: false).
2
The page of laws, by session.code and then sessionLawId, both as text in the database's collation, not chapter order (so ..._C10 comes before ..._C2; sort by number client-side if you need chapter order). The record without its dates list, sources or text: fetch those with GET /us/session-laws/{sessionLawId}.
Show child attributes
Show child attributes
Rows on this page. Never a total: the list is paged by cursor and has no corpus-wide count.
25
Opaque cursor for the next page: send it as cursor, with the same filters. Null on the last page.
"eyJ2IjoxLCJqIjoibW4iLCJzIjoiMjAyNVIiLCJpIjoiU1NMX01OXzIwMjVSX0dfWTIwMjVfQzEzIn0"
True when another page follows. Each page is charged, an empty one included, except an empty page that coverage reports as a gap in our collection.
true
Present only on an EMPTY page: what we hold of the session (or state) you asked about, so you can tell nothing matches your filters from we have not collected this. status: complete means the first: the page is charged. partial (the collection stopped short) and not_held (we hold nothing of it) mean the second: the page is NOT charged and creditsConsumed is 0. Omitted when the coverage cannot be read, in which case the empty page is charged as before.
Show child attributes
Show child attributes
Was this page helpful?

