---
id: CAP-PFM-QRY-014
title: Download the complete answer to a query
feature: FEAT-PFM-QRY-05
product_area: platform
module: queries
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-RPT-PRN-007
  - CAP-RPT-PRN-008
  - CAP-RPT-PRN-009
  - CAP-RPT-PRN-011
  - CAP-RPT-PRN-012
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-platform:docs/migrations/query-scheduling-exports/README.md#D1. One execution path for
    interactive, exported and scheduled queries
  - 'services-platform:docs/migrations/query-scheduling-exports/README.md#D5. Permissions'
  - >-
    services-platform:docs/migrations/query-scheduling-exports/README.md#D7. Delivery: one e-mail per
    recipient with a secure link, through the communications module
  - >-
    services-platform:docs/migrations/query-scheduling-exports/README.md#D9. Exports: two formats, independent
    heading and description options
  - >-
    services-platform:docs/migrations/query-scheduling-exports/README.md#D10. One asynchronous path, which
    feels synchronous for small exports
  - >-
    services-platform:docs/migrations/query-scheduling-exports/README.md#D11. Storage: the Rhythm 1.0 bucket,
    under its protected prefix
  - >-
    services-platform:docs/migrations/query-scheduling-exports/README.md#Remaining differences (accepted by
    Andrew 2026-10-17)
purpose: Write the query’s full result to a spreadsheet file while preserving its order and checking access again.
outcomes:
  - The complete result can be processed outside the screen.
  - Generation rechecks the requester’s data access.
  - Download paths use the export’s permitted audience.
concepts:
  - CON-PFM-QRY-004
actors:
  - ACT-PFM-013
  - ACT-PFM-007
  - ACT-PFM-012
surfaces:
  - console
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: platform.queries.read
    where_checked: api
    scope: tenant
  - key: <application>.<dataset>.query (the owner's key)
    where_checked: service
    scope: tenant
  - key: platform.query-schedules.manage (to follow and download anyone's export)
    where_checked: service
    scope: tenant
rules:
  - id: BR-CAP-PFM-QRY-014-01
    proved_by: [ATS-PFM-QRY-014-01, TST-PFM-QRY-301, TST-PFM-QRY-302]
    statement: >-
      Query export uses the same access as query execution with no extra export permission, validating the
      request before creating an export.
    kind: permission
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
      a caller who may not query the dataset (403)
    origin: 'services-platform:docs/migrations/query-scheduling-exports/README.md#D5. Permissions'
  - id: BR-CAP-PFM-QRY-014-02
    proved_by: [ATS-PFM-QRY-014-02, TST-PFM-QRY-301]
    statement: >-
      Exports require exactly one saved-query source with values or an on-screen definition, support CSV or
      Excel and label, technical-name or absent headings, allow descriptions only in Excel, and default to
      Excel with labels without descriptions.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
      a body naming both a saved query and a definition (400)
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D9. Exports: two formats,
      independent heading and description options
  - id: BR-CAP-PFM-QRY-014-03
    proved_by: [ATS-PFM-QRY-002-19, ATS-PFM-QRY-014-03, ATS-PFM-QRY-014-11, TST-PFM-QRY-301, TST-PFM-QRY-302]
    statement: >-
      Exports queue asynchronously and read the query engine in pages of one thousand to write its complete
      result once in its column and row order.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
      every planted row exactly once, in the query’s sort, with no gap
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D1. One execution path for
      interactive, exported and scheduled queries
  - id: BR-CAP-PFM-QRY-014-04
    proved_by: [ATS-PFM-QRY-014-04, TST-PFM-QRY-394]
    statement: >-
      CSV uses UTF-8 with a byte-order mark, CRLF and quoting, calendar dates and workspace-offset instants;
      Excel retains native value types, and formula-like text is neutralized without altering negative
      numbers.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#prefixes a value
      starting with = with an apostrophe
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D9. Exports: two formats,
      independent heading and description options
  - id: BR-CAP-PFM-QRY-014-05
    proved_by: [ATS-PFM-QRY-014-05, TST-PFM-QRY-302]
    statement: >-
      Exports fail with an actionable message above one million CSV rows, Excel’s sheet capacity or fourteen
      minutes of work.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
      too_many_rows with the sentence a person reads
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D9. Exports: two formats,
      independent heading and description options
  - id: BR-CAP-PFM-QRY-014-06
    proved_by: [ATS-PFM-QRY-014-06, TST-PFM-QRY-302]
    statement: >-
      Export execution rechecks query access and produces no file when it is lost, while Rhythm staff need a
      current workspace grant.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
      access_lost for staff holding no grant into the tenant
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D4. Whose access a run uses: the
      schedule's owner, checked fresh at every run
  - id: BR-CAP-PFM-QRY-014-07
    proved_by: [ATS-PFM-QRY-013-14, ATS-PFM-QRY-014-07, ATS-PFM-QRY-014-12, ATS-PFM-QRY-018-15, TST-PFM-QRY-108, TST-PFM-QRY-301]
    statement: >-
      Export status and downloads are available to the requester, the relevant schedule owner and schedule
      managers; others receive not found, unfinished files return not ready, and expired files return gone.
    kind: permission
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
      404 to a colleague who did not ask for it, even once the file exists
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D7. Delivery: one e-mail per
      recipient with a secure link, through the communications module
  - id: BR-CAP-PFM-QRY-014-08
    proved_by: [ATS-PFM-QRY-014-08, TST-PFM-QRY-109, TST-PFM-QRY-302]
    statement: >-
      Files remain for thirty days under their workspace and export identities, and download redirects last
      five minutes.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#records
      the rows, the bytes, the completion and the expiry 30 days later
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D11. Storage: the Rhythm 1.0
      bucket, under its protected prefix
  - id: BR-CAP-PFM-QRY-014-09
    proved_by: [ATS-PFM-QRY-014-09, ATS-PFM-QRY-014-13, ATS-PFM-QRY-015-20, TST-PFM-QRY-301, TST-PFM-QRY-395]
    statement: >-
      Personal delivery links need no login, bind workspace, export, recipient and a seven-day expiry, require
      current recipient membership and an existing file, record downloads, and otherwise return an
      invalid-link page.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
      the plain page for a recipient no longer on the schedule
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D7. Delivery: one e-mail per
      recipient with a secure link, through the communications module
  - id: BR-CAP-PFM-QRY-014-10
    proved_by: []
    statement: Interactive and scheduled exports use separate queues and concurrency allocations.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D10. One asynchronous path, which
      feels synchronous for small exports
    origin: >-
      services-platform:docs/migrations/query-scheduling-exports/README.md#D10. One asynchronous path, which
      feels synchronous for small exports
  - id: BR-CAP-PFM-QRY-014-11
    proved_by: [ATS-PFM-QRY-014-10, TST-PFM-QRY-302]
    statement: >-
      Exported references show their display values with identity columns only when selected, and an empty
      result still writes headings.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
      no id at all when the Spouse ID is not selected
    origin: none
scenarios:
  - id: SC-CAP-PFM-QRY-014-01
    given: A query export requests technical-name headings.
    when: The worker writes the result file.
    then: Headings retain the query’s output-column order.
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#heads
      the file with the technical names in the query’s output order
edge_cases: []
constraints: []
depends_on:
  - CAP-PFM-QRY-002
related:
  - CAP-PFM-QRY-002
  - CAP-PFM-QRY-013
  - CAP-PFM-QRY-015
journeys:
  - JNY-PFM-QRY-002
open_questions:
  - OQ-PFM-QRY-002
operations:
  - startQueryExport
  - getQueryExport
  - downloadQueryExport
  - downloadQueryExportByLink
tables:
  - query-exports.table.ts
  - query-export-requests.table.ts
evidence:
  - 'Pinned reference: services-platform:docs/migrations/query-scheduling-exports/README.md'
  - >-
    Pinned reference:
    services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js
  - >-
    Pinned reference:
    services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js
  - 'Pinned reference: services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts'
controls: [TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-005, TC-IAM-001, TC-IAM-002, TC-IAM-003, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-005, TC-MON-001, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-QRY-05-01, FAS-PFM-QRY-05-02, FAS-PFM-QRY-05-03, FAS-PFM-QRY-05-04, FAS-PFM-QRY-05-05, FAS-PFM-QRY-07-02]
suites: [TST-PFM-QRY-108, TST-PFM-QRY-109, TST-PFM-QRY-301, TST-PFM-QRY-302, TST-PFM-QRY-394, TST-PFM-QRY-395]
---

# Download the complete answer to a query

## Purpose

Write the query’s full result to a spreadsheet file while preserving its order and checking access again.

## How It Works

A staff member chooses export for the query they have on screen or have saved. They select a supported format and heading options. Platform validates the question and queues file generation. The worker checks access again and pages through the complete result. Writers preserve result ordering and handle values safely for spreadsheets. Row and duration limits can stop generation with a useful failure message. The requester follows the export until its file is ready. A download then redirects to a short-lived storage link. Scheduled recipients use their own delivery links.

## Relationships

Query files use ordinary query execution and export records. Scheduled delivery reuses the file-building path.
