Get many state session laws at once, by id or citation
Read up to 50 STATE session laws in one call: the same record GET /us/session-laws/{sessionLawId} returns, for each id or citation in ids, in the order you sent them. Use it when you are enriching a list (a sync job holding ids from /us/session-laws/list or /us/session-laws/changes, or an agent handed a memo with a dozen session-law citations) instead of making a round trip per law.
When to use this instead of POST /us/statutes/resolve. resolve identifies a citation: it costs 2 credits per item and answers with a sessionLaw match block (title, dates, source link), never the record or its text. This route returns the FULL record of each law, the same as GET /us/session-laws/{sessionLawId} (2 credits per law served), and optionally the first page of its text. Use resolve to find the id; use this route to read many laws in one request instead of one call each, which matters under the default limit of 30 requests per minute per key (a plan raises it).
Cost: 2 credits per law served, the single-lookup price. Batching buys a round trip, not a discount. A miss is not charged, and creditsConsumed reports what was actually billed. A citation in ids also pays the /us/statutes/resolve price (2 credits), as its own ledger line, exactly as GET /us/session-laws/{sessionLawId} charges a citation: kept whenever we gave a verdict (a served law, an unresolved or ambiguous citation) and refunded when the session it points into is only partly collected, or when we failed. An id never pays it.
Every entry gets an answer. A served law is in laws; every other entry is in notFound with a status (not_found, unparsed, ambiguous, needs_jurisdiction, session_not_fully_collected, federal, or error), the finer reason the single route's 404 carries, the same detail sentence, and the candidates where there are any. Nothing is dropped silently, so a shorter laws array can always be explained: every distinct entry is in laws, in notFound, or, when it names a law an earlier entry already produced, in resolved.
Mapping your inputs. laws holds only the laws served, so it cannot be zipped back onto ids by position. Each law carries the input it answers, and resolved lists every input that did not match an id exactly as sent (a citation, or an id cleaned of whitespace, quotes or a trailing period) with the id it resolved to. Duplicates are collapsed first: an entry identical to an earlier one after trimming is dropped, order is kept, and deduped says how many. Two DIFFERENT spellings of one law (S.F.No. 1552 and its sessionLawId) serve the law once, under the first, bill it once, and list both in resolved.
Statuses. resolved: served. not_held: served as a record, because the registry knows the law and holds no text (textStatus: not_held). not_found: no law carries the id. unparsed: it reads as a citation no state session law we hold carries. ambiguous: the citation names several laws, all in didYouMean, none chosen for you (Minnesota 2025 has two Chapter 1s). needs_jurisdiction: a form several states share, naming none: send jurisdiction or name the state. session_not_fully_collected: nothing matched in a session we have only partly collected, so the law may exist; never charged, the resolve price included. federal: federal acts are served by /us/statutes/section. error: WE failed to look this entry up. It says nothing about the law, is refunded in full, and is safe to resend.
jurisdiction scopes how every citation in the batch is read, exactly as the same parameter does on the single route. It is validated before anything is charged: a bad code is a 422.
includeBody adds the FIRST page of each served law's text (about 30,000 characters) as body, at the body price (6 credits) per law whose page is served, charged after the work because the count is not knowable up front. It multiplies with the batch: 50 laws at 2 + 6 is 400 credits in one call. A law whose text is withheld, not held or empty returns body: null and is not charged for it. If your balance cannot cover the pages after the records were served, the records are returned without bodies rather than taking the paid-for result away. bodyNextCursor continues a longer act on GET /us/session-laws/{sessionLawId}/body.
Limits. At most 50 DISTINCT entries after duplicates collapse (a raw list of up to 200 is accepted so a run of repeats is not refused), each at most 300 characters. An empty list, an over-long entry, more than 50 distinct entries or a bad jurisdiction is a 422 and nothing is charged. At most 8 lookups run at once inside a call.
Example
curl -X POST "https://api.vaquill.ai/api/v1/us/session-laws/batch" \
-H "Authorization: Bearer $VAQUILL_API_KEY" -H "Content-Type: application/json" \
-d '{"ids": ["SSL_MN_2025R_G_Y2025_C1", "S.F.No. 1552"], "jurisdiction": "mn"}'
const res = await fetch("https://api.vaquill.ai/api/v1/us/session-laws/batch", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VAQUILL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ids: ["SSL_MN_2025R_G_Y2025_C1", "S.F.No. 1552"], jurisdiction: "mn" }),
});
const { laws, notFound, resolved } = await res.json();
const byInput = new Map(laws.map((law) => [law.input, law]));
Related endpoints
- GET /us/session-laws/{sessionLawId} is the single read, with the same record and rules.
- GET /us/session-laws/list and GET /us/session-laws/changes are where the ids come from.
- GET /us/statutes/resolve points a session-law citation here.
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.
Rate limits. Calls count against your key's per-minute budget like every Data API route; a 429 carries Retry-After.
Request
- Base URLhttps://api.vaquill.ai
- Auth
- HTTP bearer
- or API key in header X-API-Key
- TypeScriptA function with typed inputs. Its response is checked against the schema.
Headers
Optional. 16 to 255 visible ASCII characters (a UUIDv4 is ideal). A retry with the same key and the same request replays the original 2xx response for 24 hours without charging again. The same key with a different request is rejected. Scope is the API key, method, path and body. The stored response body is a query-derived cache controlled by the account's data retention setting cacheQueryDerivedData: when it is false the body is never stored and a retry answers 409 idempotency_replay_unavailable, without charging again.
Request body
Example request
{
"ids": [
"SSL_MN_2025R_G_Y2025_C1",
"S.F.No. 1552",
"SSL_MN_1801R_G_Y1801_C9999"
],
"jurisdiction": "mn",
"includeBody": true
}Response
One entry per distinct id or citation: the served laws in laws, every other entry in notFound with its status and reason. Not every entry is a law: read notFound, and creditsConsumed for what was billed.
Example response
{
"laws": [
{
"sessionLawId": "SSL_MN_2025R_G_Y2025_C1",
"jurisdiction": "mn",
"citation": "CHAPTER 1--S.F.No. 1552",
"citableAs": "Minn. Laws 2025, ch. 1",
"citationShort": "MN ch. 1 (2025)",
"session": {
"code": "2025R",
"label": "2025 Regular Session",
"type": "regular",
"year": 2025,
"coverageStatus": "complete"
},
"series": "general",
"instrumentType": "act",
"number": "1",
"billKey": "HB2",
"billNumber": "S.F.No. 1552",
"numberingYear": 2025,
"title": "An act relating to agriculture; modifying financial reporting requirements for grain buyers; amending Minnesota Statutes 2024, section 223.17, subdivision 6.",
"shortTitle": "Enact Advance Ohio Higher Education Act",
"approvedDate": "2025-03-17",
"effectiveFirst": "2025-09-01",
"effectiveLast": "2026-01-01",
"enactmentOutcome": "signed",
"isLaw": true,
"textStatus": "held",
"withheldReason": "LA: struck (deleted) law is merged into the text as if current while the record declares the markup preserved or absent (audit 2026-10-03). Awaiting a re-run with strike and underline recovery.",
"charCount": 5470,
"estimatedBodyPages": 1,
"textSha256": "55c7a8d08647e41f87e23fc2016881a6583911a1d5cfa8687403442e9096451b",
"versionNo": 1,
"amendatoryMarkup": "preserved",
"markupConvention": "publisher_literal",
"documentKind": "session_law_print",
"identityConfidence": "single_source",
"officialUrl": "https://www.nysenate.gov/legislation/bills/2025/A414",
"officialUrlBasis": "bill_page",
"licenseNote": "Government public: published by a government body with no restrictive terms, so the text may be reused, including commercially.",
"updatedAt": "2026-10-05T09:06:39.998043+00:00",
"citationAsPrinted": "CHAPTER 1--S.F.No. 1552",
"vetoStatusAsPrinted": "Signed by the governor March 17, 2025, 10:54 a.m.",
"pageCount": 162,
"sourceUrl": "https://www.revisor.mn.gov/laws/2025/0/Session+Law/Chapter/1/",
"effectiveDates": [
{
"date": "2024-12-05",
"dateAsPrinted": "December 5, 2024"
}
],
"otherDates": [
{
"date": "2025-03-17",
"dateAsPrinted": "March 17, 2025, 10:54 a.m.",
"kind": "approved"
},
{
"date": "2025-03-14",
"dateAsPrinted": "March 14, 2025",
"kind": "other",
"kindAsPrinted": "Presentment Date"
}
],
"codeSectionsAffected": [
{
"actSectionAsPrinted": "6",
"actionAsPrinted": "AMD",
"codeAsPrinted": "RCW",
"sectionAsPrinted": "19.27A.020"
}
],
"sources": [
{
"byteLength": 69907,
"contentType": "text/html; charset=utf-8",
"fetchedAt": "2026-09-29T00:41:01Z",
"hostClass": "gov",
"role": "text",
"sha256": "6c83d9c109c1ec61fcd96cea92c8c48414df7ae7dfda7452ebcfbddc181b6b45",
"sourceIndex": 0,
"url": "https://www.revisor.mn.gov/laws/2025/0/Session+Law/Chapter/1/"
}
],
"publisher": {
"legislatureNumber": 94,
"sessionLastYear": 2025,
"printedPageStart": "1",
"printedPageEnd": "1",
"documentKindAsPrinted": "Enrolled version",
"publisherIds": [
{
"name": "RAT#",
"value": "5"
}
],
"otherCitations": [
"Act No. 1"
],
"publisherFields": {
"Actions": [
{
"Action Chamber": "E",
"Action Comment": "",
"Action Date": "04/23/2025",
"Action Description": "Effective on 9/1/25",
"Action Time": "",
"Journal Page Number": ""
},
{
"Action Chamber": "E",
"Action Comment": "",
"Action Date": "04/23/2025",
"Action Description": "Signed by the Governor",
"Action Time": "",
"Journal Page Number": "1441"
},
{
"Action Chamber": "E",
"Action Comment": "",
"Action Date": "04/14/2025",
"Action Description": "Sent to the Governor",
"Action Time": "",
"Journal Page Number": "1265"
}
],
"Bill": "14",
"Caption Version": "Enrolled",
"Chapter": "1",
"Last Action": "04/23/2025 E Effective on 9/1/25"
},
"publisherFieldsOmitted": []
},
"provenance": {
"textMethod": "pdf_text_layer",
"pagesWithText": 3,
"volume": {
"url": "https://static.legmt.gov/Laws/MCA-Supplements/2025SessionLaws_VOL1_Updated.pdf",
"pageStart": 135,
"pageEnd": 135,
"boundaryHeading": "CHAPTER NO. 1"
},
"caveats": [
"Chapter number is from the publisher's Passed Legislation index ('Current Status'); the enrolled PDF prints a blank 'Chapter No. ____' field.",
"The 1-25 line-number column printed on every page is stripped."
]
},
"extrasUnavailable": true,
"input": "S.F.No. 1552",
"status": "resolved",
"body": "CHAPTER 1--S.F.No. 1552\n\nAn act\n\nrelating to agriculture; ...",
"bodyPageEnd": 5470,
"bodyTotalChars": 5470
}
],
"resolved": [
{
"input": "S.F.No. 1552",
"sessionLawId": "SSL_MN_2025R_G_Y2025_C1",
"via": "citation",
"matchedForm": "bill"
}
],
"notFound": [
{
"input": "SSL_MN_1801R_G_Y1801_C9999",
"status": "not_found",
"reason": "not_in_registry",
"detail": "Session law not found. No law in the registry carries this id. Take ids from a /us/session-laws/list result, or send the citation in its place.",
"didYouMean": [
"SSL_KY_2025R_G_Y2025_C100",
"SSL_MO_2025R_G_Y2025_BHB2"
],
"jurisdictions": [
"il",
"mn",
"tx"
],
"candidateCount": 22,
"truncated": true,
"sessionCoverage": {
"code": "2025R",
"label": "2025 Regular Session",
"type": "regular",
"year": 2025,
"status": "complete",
"servedCount": 39,
"registryCount": 39,
"collectedCount": 39,
"denominator": {
"kind": "complete_listing",
"value": 296,
"derivation": "The publisher's complete listing of this session names 296 acts."
},
"numberingHoles": 12,
"measuredAt": "2026-10-05T09:07:12+00:00"
}
}
],
"count": 2,
"creditsConsumed": 4,
"processingTimeMs": 512.3
}Changes
No recorded changes to this operation across all 1 revision of this API.