v1 · Bulk data
/docs/exports

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:

GET /v1/exports/members···
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.

GEThttps://api.archivist.dev/v1/exports/bills
Bulk exports
Mirror the full bills dataset as one gzip-encoded NDJSON stream. One call, one stream — no query parameters or cursor to follow. One authenticated call, one backpressure-friendly stream, and no client-side cursor loop.

Download with curl

Use --compressed so curl decodes the gzip response before writing plain NDJSON to bills.ndjson.gz or piping it to jq.

curl···
# 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.

export-bills.js···
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.

Response schema
FieldTypeRequiredDescription
status200 OK
required
The export stream opened successfully.
Content-Typeapplication/x-ndjson
required
The decoded response body is newline-delimited JSON.
Content-Encodinggzip
required
The body is gzip encoded on the wire. curl --compressed and standard fetch clients decode it before exposing the body.
Content-Dispositionattachment
required
Provides the download filename bills.ndjson.gz.
bodyNDJSON stream
required
One BillItem object per line, with each line terminated by "\n".
body[].idstring
required
Stable bill id in the form {congress}-{type}-{number}.
body[].congressinteger
required
Congress number, for example 118 or 119.
body[].typestring
required
Bill type, for example hr, s, or sjres.
body[].numberinteger
required
The bill number within its type and Congress.
body[].officialTitlestring
required
Official title from the bill mirror.
body[].introducedDatestring | null
required
ISO 8601 calendar date, or null when unavailable.
body[].memberNamestring | null
required
Sponsor name, or null when no member is linked.
body[].memberBioguideIdstring | null
required
Sponsor Bioguide id, or null when unavailable.
body[].memberByCongressKeystring | null
required
Congress-scoped sponsor key, or null when unavailable.
body[].committeeNamesstring
required
Comma-separated committee names from the mirror.
body[].sourceUrlstring | null
required
Canonical source URL, or null when unavailable.
body[].lastSyncedAtstring
required
ISO 8601 timestamp for the last mirror sync.
response.ndjson···
{"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.
GEThttps://api.archivist.dev/v1/exports/members
Members export
Mirror the full members dataset as one gzip-encoded NDJSON stream. One authenticated call, one backpressure-friendly stream, and no client-side cursor loop.

Download with curl

Use --compressed so curl decodes the gzip response before writing plain NDJSON to members.ndjson.gz or piping it to jq.

curl···
# 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.

export-members.js···
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.

Response schema
FieldTypeRequiredDescription
status200 OK
required
The export stream opened successfully.
Content-Typeapplication/x-ndjson
required
The decoded response body is newline-delimited JSON.
Content-Encodinggzip
required
The body is gzip encoded on the wire. curl --compressed and standard fetch clients decode it before exposing the body.
Content-Dispositionattachment
required
Provides the download filename members.ndjson.gz.
bodyNDJSON stream
required
One MemberExportLine object per line, with each line terminated by "\n".
body[].idstring
required
The persisted Member.id, which is also the Bioguide join key in this mirror.
body[].namestring
required
Member name from the mirror.
body[].partystring | null
required
Latest member party from Member, or null when unavailable.
body[].statestring | null
required
Latest member state from Member, or null when unavailable.
body[].updatedAtstring
required
ISO 8601 timestamp from Member.updatedAt.
body[].termsMemberExportTerm[]
required
MemberTerm rows for this member, ordered newest first by Congress.
body[].terms[].idstring
required
Persisted MemberTerm id.
body[].terms[].memberBioguideIdstring
required
MemberTerm Bioguide join key.
body[].terms[].congressinteger
required
Congress number for the term.
body[].terms[].partystring | null
required
Party recorded for the term, or null when unavailable.
body[].terms[].statestring | null
required
State recorded for the term, or null when unavailable.
body[].terms[].chamberstring
required
Chamber recorded for the term.
response.ndjson···
{"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.