> ## Documentation Index
> Fetch the complete documentation index at: https://docs.champ.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Download case files

> Fetch files a workflow attached to a case from your own backend

Workflows can attach files to a case — a document downloaded by a browser step, a file pulled from a URL or
Google Drive. When the workflow **exports** such a file, your backend can download it through a stable URL using
the same API token as the [cases API](/integrations/cases-api).

The flow is:

1. In the workflow, a **File** step attaches the file to the case with **Export file** enabled.
2. The workflow writes the file's URL into a case attribute (a **Case update** step referencing the File step's
   `url` output).
3. Your backend reads the attribute from the [cases API](/integrations/cases-api#fetch-a-case) and fetches the
   URL with your API token.

## The file URL

An exported case file is addressed by a stable URL:

```
https://download.champ.ai/f/{FILE_ID}
```

The URL never expires and never changes — it's safe to store in your own database and fetch years later. It also
grants nothing by itself: anyone holding the URL without a valid API token gets a `401`, and even a valid token
from a different organization gets a `404`, so passing it around in tickets, emails, or logs doesn't expose the
file.

## Download a file

Send a `GET` request with your API token:

```bash theme={null}
curl -L https://download.champ.ai/f/{FILE_ID} \
  -H "Authorization: Bearer <YOUR_API_TOKEN>"
```

<ParamField path="FILE_ID" type="string" required>
  Path parameter. The file's ID — the last segment of the URL the workflow wrote into the case attribute. You
  never construct this yourself; always use the full URL the workflow produced.
</ParamField>

<ParamField path="Authorization" type="string" required>
  Request header. `Bearer <YOUR_API_TOKEN>` — an API-scoped token from the
  [Tokens page](https://dash.champ.ai/app/tokens), the same token the cases API uses.
</ParamField>

The response is a `302` redirect: `Location` holds a short-lived storage link (valid for about a minute) that
serves the bytes, and the body carries the same link as JSON for clients that prefer an explicit second request:

```json theme={null}
{ "url": "https://<storage-host>/…?signature=…" }
```

Most HTTP clients follow the redirect automatically and just receive the file — modern curl, `fetch`,
python-requests, and Go all drop the `Authorization` header on the cross-host hop, which is exactly right.

<Warning>
  If your client forwards the `Authorization` header to the redirect target, the storage host rejects the request
  with a `400` — it refuses requests carrying two credentials. If you hit that, disable automatic redirects and
  make two explicit requests: `GET` the file URL with your token, then `GET` the `url` from the JSON body (or the
  `Location` header) with **no** `Authorization` header.
</Warning>

The final response serves the file with its original filename in `Content-Disposition`.

## Exporting a file

Only **exported** files can be downloaded externally. Export is a per-file decision the workflow makes: the
**External access** setting on the File step. A file attached as **Internal only** stays internal — its URL still
works inside the workflow (for example as an LLM file input), but external requests get a `404`.

Once a run exports a file it stays exported; retries of the step don't change that.

<Note>
  There is no listing endpoint — your systems learn about a file when the workflow hands its URL over, normally
  through a case attribute you read from the cases API or receive in a
  [webhook payload](/webhooks/payloads).
</Note>

## Pre-signed links

If your systems can't call an authenticated API at all — the file needs to reach a human clicking a link in a
ticket or email — the File step offers a third mode: **Export + pre-signed link**. Besides exporting the file, the
step's output then includes a second URL, `presigned_url`, that serves the file directly with **no API token**.

Trade-offs versus the stable URL:

* **It expires after 7 days.** Don't store it — anyone clicking later gets an error. The stable URL on the same
  output never expires.
* **It grants access by itself.** Anyone holding the link can download the file until it expires, so treat it
  like the file's contents, not like an identifier.
* **It can't be revoked** — the only control is the 7-day expiry.

Use the stable URL whenever your backend can send an API token; reach for the pre-signed link only when it can't.

## Errors

| Status | When                                                                                                                              |
| ------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing or invalid `Authorization` header.                                                                                        |
| `404`  | The file doesn't exist, belongs to another organization, or hasn't been exported. These cases are deliberately indistinguishable. |

Errors return a JSON body with an `error` message.
