---
url: /api/get-file.md
---
# Get a File

Returns a short-lived download link for one file on a request you raised. Use it to download:

* the files you sent when you [created the request](/api/create-request#attaching-files),
* the proof of payment the cashier uploads when paying,
* the EFD receipt,
* any file people attach to the request in Johari (for example an accountant's upload),
* any file your system adds with [Add Files](/api/add-files).

## Endpoint

| Method | URL |
|---|---|
| `GET` | `/api/requests/:requestID/files/:fileID` |

| Path parameter | Description |
|---|---|
| `requestID` | The request's `requestID` |
| `fileID` | A `files[].id` from [Get a Request](/api/get-request#files) or from a [callback](/guide/callbacks#what-we-send) |

Sign it like any `GET`, with an empty body:

```
<timestamp>.GET./api/requests/6650f1a2b3c4d5e6f7a8b9e1/files/0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f.
```

You can only see requests raised with **your** API key, and their files.

## Response

`data` on success:

| Field | Type | Description |
|---|---|---|
| `id` | `string` | The file's ID |
| `name` | `string` | The file name, e.g. `"invoice-1042.pdf"` |
| `type` | `string \| null` | The file type, e.g. `"application/pdf"`, or `null` |
| `size` | `number \| null` | The size in bytes, or `null` |
| `url` | `string` | A direct download link. Valid for **5 minutes** |
| `expiresAt` | `string` | When `url` stops working (ISO 8601, UTC) |

## Downloading the file

`url` is a pre-signed storage link. Download it with a plain `GET` **straight away**:

* Send **no** Johari headers: no `x-api-key`, `x-timestamp` or `x-signature`. Do not sign it.
* It works for 5 minutes, until `expiresAt`.
* Do not store `url`. Ask this endpoint for a fresh link each time you need the file.

## Example

::: code-group

```bash [cURL]
REQUEST_ID="6650f1a2b3c4d5e6f7a8b9e1"
FILE_ID="0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f"
TS=$(date +%s)
SIG=$(printf '%s' "$TS.GET./api/requests/$REQUEST_ID/files/$FILE_ID." \
  | openssl dgst -sha256 -hmac "$SIGNING_SECRET" \
  | sed 's/^.* //')
curl "$BASE_URL/api/requests/$REQUEST_ID/files/$FILE_ID" \
  -H "x-api-key: $API_KEY" \
  -H "x-timestamp: $TS" \
  -H "x-signature: $SIG"

# Then download the link from data.url, with no Johari headers:
# curl -o invoice-1042.pdf "<data.url>"
```

```js [Node.js]
const fs = require("fs");

const requestID = "6650f1a2b3c4d5e6f7a8b9e1";
const fileID = "0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f";

// callJohari is from the Signing page
const result = await callJohari("GET", `/api/requests/${requestID}/files/${fileID}`);

if (result.httpStatus === 200) {
  // A plain GET, with no Johari headers
  const download = await fetch(result.data.url);
  fs.writeFileSync(result.data.name, Buffer.from(await download.arrayBuffer()));
} else {
  console.error(result.httpStatus, result.message);
}
```

```python [Python]
import requests  # pip install requests

request_id = "6650f1a2b3c4d5e6f7a8b9e1"
file_id = "0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f"

# call_johari is from the Signing page
result = call_johari("GET", f"/api/requests/{request_id}/files/{file_id}")

if result["httpStatus"] == 200:
    # A plain GET, with no Johari headers
    download = requests.get(result["data"]["url"])
    with open(result["data"]["name"], "wb") as f:
        f.write(download.content)
else:
    print(result["httpStatus"], result["message"])
```

:::

Response `200`:

```json
{
  "status": "success",
  "data": {
    "id": "0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f",
    "name": "invoice-1042.pdf",
    "type": "application/pdf",
    "size": 48213,
    "url": "https://storage.example.com/...",
    "expiresAt": "2026-10-30T12:05:00.000Z"
  },
  "message": "File link created successfully"
}
```

The `url` above is shortened. Use the whole value you receive, exactly as it is.

Response `404`, request not found:

```json
{
  "status": "error",
  "data": null,
  "message": "Request not found"
}
```

You get `Request not found` when the request ID is wrong, when the request was raised with a different API key, or when it was deleted in Johari.

Response `404`, file not found:

```json
{
  "status": "error",
  "data": null,
  "message": "File not found"
}
```

You get `File not found` when the request exists but has no file with that `fileID`.

The usual `401` errors apply too. See [Errors](/guide/errors#authentication-errors).

## Common mistakes

::: danger Sending Johari headers to the download link
`url` is a pre-signed storage link, not a Johari API call. Download it with a plain `GET`. Do not sign it, and send no `x-api-key`, `x-timestamp` or `x-signature` headers. Only the call to `/api/requests/:requestID/files/:fileID` is signed.
:::

::: danger Storing the link
The link stops working after 5 minutes. Store the `fileID`, and ask for a fresh link each time.
:::

## Related pages

* [Get a Request](/api/get-request)
* [Create a Request](/api/create-request#attaching-files)
* [Add Files](/api/add-files)
* [Callbacks](/guide/callbacks)
