Downloading files from a Pull
A Pull can carry two kinds of
downloadable files:
- Consumer-uploaded files — files a consumer submitted through a custom file
upload field in a consumer flow. - Documents — insurance documents (declarations pages, ID cards, binders, etc.)
associated with the Pull.
Both expose a download_url, and both are downloaded the same way. This guide covers
where to find each, then how to download them.
Consumer-uploaded files
Custom form answers are nested a few levels deep inside a Pull:
Pull
└─ answers[] → AnswerSection
└─ fields[] → AnswerField
├─ type → "FILE_UPLOAD"
└─ value[] → FileUpload (when type is "FILE_UPLOAD")
└─ download_url
- Iterate over the Pull's
answersarray. Each entry is an
AnswerSection. - Within each section, iterate over
fields. Each entry is an
AnswerField. - For any field whose
typeisFILE_UPLOAD, thevalueis an array of
FileUpload objects (one
per uploaded file). For all other field types,valueis a scalar, and isnullif
the field was never answered. - Each
FileUploaddescribes one file and includes adownload_url.
A FILE_UPLOAD field looks like this in a Pull response:
{
"consumer_flow_section_field_id": "8f1c2e6a-2b9d-4f3a-9b1e-7c5d2a4f6e10",
"label": "Upload your declarations page",
"type": "FILE_UPLOAD",
"value": [
{
"file_id": "3a7b9c1d-4e2f-4a6b-8c9d-0e1f2a3b4c5d",
"name": "declarations-page.pdf",
"type": "application/pdf",
"size": 482931,
"download_url": "https://app.usecanopy.com/api/v1.0.0/teams/:teamId/pulls/:pullId/answers/:answerId/files/:fileId"
}
]
}Documents
Documents are simpler — they live directly on the Pull's documents array, and each
Document carries its own
download_url:
Pull
└─ documents[] → Document
└─ download_url
A document looks like this in a Pull response:
{
"document_id": "5d4c3b2a-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"title": "Auto Declarations",
"document_type": "DECLARATIONS",
"policy_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
"mime_type": "application/pdf",
"download_url": "https://app.usecanopy.com/api/v1.0.0/teams/:teamId/pulls/:pullId/documents/:documentId"
}Downloading a file
Use the
download_urlexactly as returned by the API. The paths shown above are
illustrative — don't construct them yourself.
A download_url is a Canopy Connect API endpoint, not the file itself. This is true
for both consumer-uploaded files and documents. Request it with an authenticated GET,
using the same credentials as every other API call — the x-canopy-client-id and
x-canopy-client-secret headers.
The endpoint responds with a 302 redirect to a short-lived, signed URL where the
file bytes are actually hosted. You can consume this in one of two ways:
Option 1 — follow the redirect (default)
Let your HTTP client follow the 302 and stream the file directly. Most clients do this
automatically.
curl -L \
-H "x-canopy-client-id: $CANOPY_CLIENT_ID" \
-H "x-canopy-client-secret: $CANOPY_CLIENT_SECRET" \
-o declarations-page.pdf \
"https://app.usecanopy.com/api/v1.0.0/teams/:teamId/pulls/:pullId/documents/:documentId"The
-Lflag tells curl to follow the redirect. Note that the signed download URL is
short-lived — fetch the file promptly rather than storing the signed URL for later.
Option 2 — request the signed URL as JSON
If you would rather receive the signed URL instead of following the redirect (for
example, to hand it to a download manager or a client-side component), send an
Accept: application/json header. The endpoint then returns a JSON body containing the
signed URL:
curl \
-H "x-canopy-client-id: $CANOPY_CLIENT_ID" \
-H "x-canopy-client-secret: $CANOPY_CLIENT_SECRET" \
-H "Accept: application/json" \
"https://app.usecanopy.com/api/v1.0.0/teams/:teamId/pulls/:pullId/documents/:documentId"{
"url": "https://signed-storage.example.com/...&signature=...&expires=..."
}The returned url is the same short-lived signed location the 302 would have pointed
to. It does not require the Canopy Connect authentication headers, but it does expire, so
use it promptly.

