Resource file downloads

Retrieve an existing file using the email/hash pair supplied by its download link. The pair grants access to the matched record; resource public keys and member tokens are not substitutes.

TaskOperation
Download a supplied resource fileDownload

Example clients and synthetic values follow response conventions.

Retrieve a resource file

GET /api/v1/resource/file/download

Stream stored bytes from the configured private storage bucket. After sending the bytes, this GET attempts to update the download count.

Before you call

Guest ACL permits the action. There is no member/resource requirement or owner/resource/active/private/expiry/download-limit check in its file selection. It matches only file_hash and normalized email. Use the existing supplied link pair, with access controlled by your trusted application; do not generate a hash or substitute a resource key. The email is visible in query transport.

The action uses configured private_bucket and the matched record key, ignoring the record’s own bucket/storage/private fields. File selection and metadata do not establish provider storage availability. After echoing bytes, count_downloads is incremented from the previously read value and saved unchecked; no exact concurrent count guarantee. Storage/output/local-save failures can yield partial bytes or different state.

Request

Bodyless; no token/resource headers required.

NameLocationType / requirementMeaning
emailqueryrequired valid email, max 60 characters before normalizationSupplied link recipient; sanitized email/trim/lower for matching.
hashqueryrequired string, max 32 characters before normalizationSupplied file_hash; sanitized string/trim/striptags. No client hash derivation or exact 32-character validation.

Result

Success streams bytes directly and terminates, with no JSON wrapper or redirect. The handler does not explicitly set a success HTTP status.

Header / outputType / presenceMeaning
Content-Typestorage-supplied stringForwarded from storage ContentType; no universal MIME type or file extension inference.
Content-Dispositionattachment; filename=stored original_nameOriginal filename appended without a universal quoted filename/encoding guarantee.
PragmapublicExplicit header value; no expiry guarantee.
Bodybinary/file bytesMatched storage object body; do not unconditionally parse JSON.

Use the email/hash from an existing link; RESOURCE_FILE_HASH is an obvious placeholder supplied separately, not a generated credential. The expected result is stored bytes and its media type, with the filename from the matched record. No synthetic JSON success body is fabricated.

curl --get "${WALLKIT_API_BASE}/api/v1/resource/file/download" \
  --data-urlencode "email=reader@example.com" \
  --data-urlencode "hash=${RESOURCE_FILE_HASH}" \
  --dump-header download-headers.txt --output download-body.bin
# Inspect HTTP status and Content-Type before interpreting download-body.bin.
const url = new URL(`${process.env.WALLKIT_API_BASE}/api/v1/resource/file/download`);
url.searchParams.set("email", "reader@example.com");
url.searchParams.set("hash", process.env.RESOURCE_FILE_HASH);
const response = await fetch(url);
const media = response.headers.get("content-type") || "";
if (media.includes("application/json")) {
  console.log(response.status, await response.json());
} else if (!response.ok) {
  console.log(response.status, await response.text());
} else {
  const bytes = await response.arrayBuffer();
  console.log(response.status, media, bytes.byteLength);
  // Inspect expected media/content; a non-JSON body is not proof of a complete file.
}
import os, json
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError

query = urlencode({"email": "reader@example.com", "hash": os.environ["RESOURCE_FILE_HASH"]})
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/resource/file/download?" + query, method="GET")
try:
    with urlopen(request) as response:
        media = response.headers.get("Content-Type", "")
        body = response.read()
        if "application/json" in media:
            print(response.status, json.loads(body))
        else:
            print(response.status, media, len(body))
            # Inspect expected file content; length/status alone do not prove completeness.
except HTTPError as error:
    body = error.read()
    if "application/json" in error.headers.get("Content-Type", ""):
        print(error.code, json.loads(body))
    else:
        print(error.code, body.decode("utf-8", errors="replace"))

Illustrative header/body shape only, with media dependent on the stored object:

Content-Type: <stored ContentType>
Content-Disposition: attachment; filename=<stored original_name>
Pragma: public

<stored file bytes>

Consequential alternate

A mismatched supplied pair returns HTTP 409 JSON excerpt (other response metadata omitted):

{"error":"invalid_data","error_description":"invalid_data"}

A caught storage exception instead echoes exception text and terminates, without a stable JSON/status contract; non-JSON bytes or nominal success status alone do not prove a complete valid file.

Recovery

StatusCode / shapeCauseNext action
409invalid_event_email / invalid_event_hashEmail/hash presence, length or email validation fails.Use exact supplied link values and correct encoding.
409invalid_dataNo record matches normalized email/hash pair.Ask the link issuer for the intended existing link; a member token does not repair the pair.
UnspecifiedStorage exception text / partial output; no universal JSON envelopeStorage access fails or later handling fails.Inspect status/media/content and ask integration owner to check stored object/configuration; do not assume a completed download or blindly retry count-writing requests.

Next task

Validate expected media/content before presenting the file. For a transaction document instead, use its distinct User subscriptions and Pricing selection only when that is the supplied document type.

Full diagram

Use the arrow keys to scroll. Escape closes this view.

Search documentation

Enter at least 2 characters.

    ↑ ↓ move through results · Enter opens · Escape closes