Accessing Resource Attachment Files

Several Canvas FHIR resources include a url attribute that points to an attachment file stored in S3. When you read or search these resources, the url value will be a /files/ path on the Canvas FHIR server. Fetching that URL requires a Bearer token and returns a redirect to a pre-signed S3 URL that expires after 10 minutes.

There are two ways to retrieve the file, depending on whether you want the raw file content or the pre-signed URL itself.

Endpoints that return file URLs #

ResourceURL pattern
ConsentGET /Consent/{id}/files/sourceAttachment
DiagnosticReportGET /DiagnosticReport/{id}/files/presentedForm
DocumentReferenceGET /DocumentReference/{id}/files/content
MediaGET /Media/{id}/files/content
PatientGET /Patient/{id}/files/photo
PractitionerGET /Practitioner/{id}/files/signature

Option 1: Follow the redirect to get the file content #

The simplest approach is to let your HTTP client follow the redirect automatically. The pre-signed S3 URL contains its own authentication in the query parameters, so no additional headers are needed for the second request.

  • curl -L -o downloaded_file.pdf \
      -H "Authorization: Bearer $TOKEN" \
      "https://fumage-{instance}.canvasmedical.com/DocumentReference/abc123/files/content"
    
  • import requests
    
    base_url = "https://fumage-{instance}.canvasmedical.com"
    token = "your_bearer_token"
    
    file_url = f"{base_url}/DocumentReference/abc123/files/content"
    response = requests.get(
        file_url,
        headers={"Authorization": f"Bearer {token}"},
    )
    
    with open("downloaded_file.pdf", "wb") as f:
        f.write(response.content)
    
  • const response = await axios.get(url, {
      headers: { Authorization: `Bearer ${token}` },
      responseType: 'arraybuffer',
    });
    // response.data contains the raw file content
    writeFileSync('downloaded_file.pdf', Buffer.from(response.data));
    

Option 2: Capture the pre-signed URL without following the redirect #

If you need the pre-signed S3 URL itself — for example, to load it in an <iframe>, pass it to a frontend, or open it in a browser — you can configure your client to not follow redirects, and read the Location header in the response.

  • PRESIGNED_URL=$(curl -s -o /dev/null -w '%{redirect_url}' \
      -H "Authorization: Bearer $TOKEN" \
      "https://fumage-{instance}.canvasmedical.com/DocumentReference/abc123/files/content")
    
    echo "$PRESIGNED_URL"
    
  • import requests
    
    base_url = "https://fumage-{instance}.canvasmedical.com"
    token = "your_bearer_token"
    
    file_url = f"{base_url}/DocumentReference/abc123/files/content"
    response = requests.get(
        file_url,
        headers={"Authorization": f"Bearer {token}"},
        allow_redirects=False,
    )
    
    presigned_url = response.headers["Location"]
    # This URL works without auth and expires after 10 minutes
    
  • const response = await axios.get(url, {
      maxRedirects: 0,
      validateStatus: (status) => status === 307,
      headers: { Authorization: `Bearer ${token}` },
    });
    
    const presignedUrl = response.headers['location'];
    // This URL works without auth — use it in an iframe, open in browser, etc.
    

Note on HTTP client redirect behavior #

Per RFC 9110, compliant HTTP clients should strip the Authorization header when following a redirect to a different host. However, some HTTP clients do not do this, which can cause S3 to reject the request with a dual-auth error:

<Error>
  <Code>InvalidArgument</Code>
  <Message>Only one auth mechanism allowed; only the X-Amz-Algorithm
  query parameter or the Authorization header should be specified,
  not both.</Message>
</Error>

If you encounter this error, your client is forwarding the Bearer token to S3. Use Option 2 above to handle the redirect manually, or configure your client to strip authorization headers on cross-origin redirects.