Bulk exports
Download complete bills and members mirrors for offline copies: gzip on the wire, one newline-delimited record at a time, and no client-side cursor loop.
Authentication
Both programmatic endpoints require an API key in the Authorization header:
curl --compressed https://api.archivist.dev/v1/exports/members \
-H "Authorization: Bearer $ARCHIVIST_KEY"Missing or invalid keys return 401; revoked or suspended access returns 403; rate-limit exhaustion returns 429 with Retry-After. See Errors & limits for the shared status reference.
Stream behavior
Each endpoint has no query parameters and no cursor to follow. The server walks its full mirror internally in 500-row pages, then yields records through a pull-driven gzip pipeline so consumers can pipe the response to disk without buffering the export in memory.
Download with curl
Use --compressed so curl decodes the gzip response before writing plain NDJSON to bills.ndjson.gz or piping it to jq.
# Stream the full bills mirror; curl decodes Content-Encoding: gzip
curl --compressed --fail-with-body \
-H "Authorization: Bearer $ARCHIVIST_KEY" \
-o bills.ndjson \
https://api.archivist.dev/v1/exports/bills
# Inspect the decoded NDJSON while piping
curl --compressed --fail-with-body \
-H "Authorization: Bearer $ARCHIVIST_KEY" \
https://api.archivist.dev/v1/exports/bills \
| jq -c .Consume with JavaScript
Standard Node and browser fetch clients decode Content-Encoding: gzip before exposing response.body. Read chunks incrementally and split on newline boundaries; do not gunzip a second time.
const res = await fetch('https://api.archivist.dev/v1/exports/bills', {
headers: { Authorization: `Bearer ${process.env.ARCHIVIST_KEY}` },
});
if (!res.ok) {
const errorBody = await res.text();
throw new Error(`Archivist ${res.status}: ${errorBody}`);
}
if (!res.body) throw new Error('Archivist returned no response body');
// Standard Node/browser fetch honors Content-Encoding: gzip before exposing response.body.
// Do not add a second gunzip. Use DecompressionStream('gzip') only when a lower-level
// client explicitly exposes raw compressed bytes.
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
if (!line.trim()) continue;
const bill = JSON.parse(line);
console.log(`${bill.id} — ${bill.officialTitle}`);
}
}
buffer += decoder.decode();
if (buffer.trim()) {
const bill = JSON.parse(buffer);
console.log(`${bill.id} — ${bill.officialTitle}`);
}Response schema
The response is a stream, not a JSON envelope. Each decoded line is one BillItem object followed by \n.
| Field | Type | Required | Description |
|---|---|---|---|
| status | 200 OK | required | The export stream opened successfully. |
| Content-Type | application/x-ndjson | required | The decoded response body is newline-delimited JSON. |
| Content-Encoding | gzip | required | The body is gzip encoded on the wire. curl --compressed and standard fetch clients decode it before exposing the body. |
| Content-Disposition | attachment | required | Provides the download filename bills.ndjson.gz. |
| body | NDJSON stream | required | One BillItem object per line, with each line terminated by "\n". |
| body[].id | string | required | Stable bill id in the form {congress}-{type}-{number}. |
| body[].congress | integer | required | Congress number, for example 118 or 119. |
| body[].type | string | required | Bill type, for example hr, s, or sjres. |
| body[].number | integer | required | The bill number within its type and Congress. |
| body[].officialTitle | string | required | Official title from the bill mirror. |
| body[].introducedDate | string | null | required | ISO 8601 calendar date, or null when unavailable. |
| body[].memberName | string | null | required | Sponsor name, or null when no member is linked. |
| body[].memberBioguideId | string | null | required | Sponsor Bioguide id, or null when unavailable. |
| body[].memberByCongressKey | string | null | required | Congress-scoped sponsor key, or null when unavailable. |
| body[].committeeNames | string | required | Comma-separated committee names from the mirror. |
| body[].sourceUrl | string | null | required | Canonical source URL, or null when unavailable. |
| body[].lastSyncedAt | string | required | ISO 8601 timestamp for the last mirror sync. |
{"id":"118-hr-3076","congress":118,"type":"hr","number":3076,"officialTitle":"Civic Data Continuity Act","introducedDate":"2023-09-12","memberName":"Pelosi, Nancy","memberBioguideId":"P000197","memberByCongressKey":"118-h-P000197","committeeNames":"House Oversight,House Administration","sourceUrl":"https://www.congress.gov/bill/118th-congress/house-bill/3076","lastSyncedAt":"2026-04-22T03:11:08.412Z"}
{"id":"118-hr-3077","congress":118,"type":"hr","number":3077,"officialTitle":"…","introducedDate":null,"memberName":null,"memberBioguideId":null,"memberByCongressKey":null,"committeeNames":"","sourceUrl":null,"lastSyncedAt":"2026-04-22T03:11:08.418Z"}Operational notes
- The endpoint accepts no query parameters and exposes no cursor; one authenticated call covers the full mirror.
- The export uses one rate-limit tick per call regardless of dataset size. A 429 response includes Retry-After with the next retry window.
- Authentication and setup failures return JSON errors: 401 for missing or invalid bearer tokens, 403 for revoked or suspended access, and 500 for an unexpected setup failure.
- Content-Disposition is attachment; filename="bills.ndjson.gz". The filename describes the wire encoding; curl --compressed and native fetch expose decoded NDJSON.
- Internally the handler reads 500-row cursor pages and yields each line on demand, ordered by congress DESC, number DESC, id DESC.
- If a database or gzip failure occurs after the 200 response starts, the connection terminates; treat an incomplete NDJSON line or file as a failed export.
Download with curl
Use --compressed so curl decodes the gzip response before writing plain NDJSON to members.ndjson.gz or piping it to jq.
# Stream the full members mirror; curl decodes Content-Encoding: gzip
curl --compressed --fail-with-body \
-H "Authorization: Bearer $ARCHIVIST_KEY" \
-o members.ndjson \
https://api.archivist.dev/v1/exports/members
# Inspect the decoded NDJSON while piping
curl --compressed --fail-with-body \
-H "Authorization: Bearer $ARCHIVIST_KEY" \
https://api.archivist.dev/v1/exports/members \
| jq -c .Consume with JavaScript
Standard Node and browser fetch clients decode Content-Encoding: gzip before exposing response.body. Read chunks incrementally and split on newline boundaries; do not gunzip a second time.
const res = await fetch('https://api.archivist.dev/v1/exports/members', {
headers: { Authorization: `Bearer ${process.env.ARCHIVIST_KEY}` },
});
if (!res.ok) {
const errorBody = await res.text();
throw new Error(`Archivist ${res.status}: ${errorBody}`);
}
if (!res.body) throw new Error('Archivist returned no response body');
// Standard Node/browser fetch honors Content-Encoding: gzip before exposing response.body.
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
if (!line.trim()) continue;
const member = JSON.parse(line);
console.log(`${member.id} — ${member.name} (${member.terms.length} terms)`);
}
}
buffer += decoder.decode();
if (buffer.trim()) {
const member = JSON.parse(buffer);
console.log(`${member.id} — ${member.name}`);
}Response schema
The response is a stream, not a JSON envelope. Each decoded line is one MemberExportLine object followed by \n.
| Field | Type | Required | Description |
|---|---|---|---|
| status | 200 OK | required | The export stream opened successfully. |
| Content-Type | application/x-ndjson | required | The decoded response body is newline-delimited JSON. |
| Content-Encoding | gzip | required | The body is gzip encoded on the wire. curl --compressed and standard fetch clients decode it before exposing the body. |
| Content-Disposition | attachment | required | Provides the download filename members.ndjson.gz. |
| body | NDJSON stream | required | One MemberExportLine object per line, with each line terminated by "\n". |
| body[].id | string | required | The persisted Member.id, which is also the Bioguide join key in this mirror. |
| body[].name | string | required | Member name from the mirror. |
| body[].party | string | null | required | Latest member party from Member, or null when unavailable. |
| body[].state | string | null | required | Latest member state from Member, or null when unavailable. |
| body[].updatedAt | string | required | ISO 8601 timestamp from Member.updatedAt. |
| body[].terms | MemberExportTerm[] | required | MemberTerm rows for this member, ordered newest first by Congress. |
| body[].terms[].id | string | required | Persisted MemberTerm id. |
| body[].terms[].memberBioguideId | string | required | MemberTerm Bioguide join key. |
| body[].terms[].congress | integer | required | Congress number for the term. |
| body[].terms[].party | string | null | required | Party recorded for the term, or null when unavailable. |
| body[].terms[].state | string | null | required | State recorded for the term, or null when unavailable. |
| body[].terms[].chamber | string | required | Chamber recorded for the term. |
{"id":"P000197","name":"Pelosi, Nancy","party":"Democrat","state":"CA","updatedAt":"2026-04-22T03:11:08.412Z","terms":[{"id":"term-p000197-118","memberBioguideId":"P000197","congress":118,"party":"Democrat","state":"CA","chamber":"house"},{"id":"term-p000197-117","memberBioguideId":"P000197","congress":117,"party":"Democrat","state":"CA","chamber":"house"}]}Operational notes
- The endpoint accepts no query parameters and exposes no cursor; one authenticated call covers the full mirror.
- The export uses one rate-limit tick per call regardless of dataset size. A 429 response includes Retry-After with the next retry window.
- Authentication and setup failures return JSON errors: 401 for missing or invalid bearer tokens, 403 for revoked or suspended access, and 500 for an unexpected setup failure.
- Content-Disposition is attachment; filename="members.ndjson.gz". The filename describes the wire encoding; curl --compressed and native fetch expose decoded NDJSON.
- Internally the handler reads 500-row cursor pages and yields each member line on demand, ordered by id ASC. Terms are ordered newest first by congress DESC, then id DESC.
- This export is source-backed: Member.id is the Bioguide key, and MemberTerm has no start-date field. There is no separate member_id or fabricated term start value.
- If a database or gzip failure occurs after the 200 response starts, the connection terminates; treat an incomplete NDJSON line or file as a failed export.