---
title: "Let large downloads resume with range · Agent Wranglers"
url: https://agentwranglers.se/playbook/api-design/files-and-uploads/range-downloads
updated: 2026-10-21
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Let large downloads resume with range requests

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Files and uploads](https://agentwranglers.se/playbook/api-design/files-and-uploads.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers). Updated 2026-10-21.

- ID: API-67
- Level: Recommended
- Chapter: [Files and uploads](https://agentwranglers.se/playbook/api-design/files-and-uploads)
- Rule: Answer byte range requests with 206 on every download over 10 MB, so a client that loses the connection fetches only the missing bytes.
- How: Send `Accept-Ranges: bytes` and an `ETag` with the full answer. A client that resumes sends `Range: bytes=<first byte>-` and `If-Range` with that ETag. Answer 206 with `Content-Range` when the ETag still matches. Send the whole file with 200 when it changed, so no client glues two versions together. Answer 416 with `Content-Range: bytes */<size>` to a range past the end. Refuse overlapping ranges and long lists of small ranges, which RFC 9110 names as signs of an attack. Object storage and most file servers do this already, so serve big files through a [short-lived link](https://agentwranglers.se/playbook/api-design/files-and-uploads/signed-downloads) where you can. Uploads resume as [Make large uploads resumable](https://agentwranglers.se/playbook/api-design/files-and-uploads/resumable-uploads) says.
- Check: A test downloads a 20 MB export with `Range: bytes=0-99` and gets 206, a matching `Content-Range` and exactly 100 bytes. The same request with a stale `If-Range` gets 200 and the whole file. A range past the end gets 416.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
import express from 'express';

const app = express();

app.get('/exports/:exportId', async (req, res) => {
  const filePath = await exportFileFor(req.user, req.params.exportId);
  res.sendFile(filePath, { acceptRanges: true, etag: true, lastModified: true });
});
```

### Go example

```go
func serveExport(w http.ResponseWriter, r *http.Request) {
	file, info, err := openExportFor(r.Context(), r.PathValue("exportID"))
	if err != nil {
		http.NotFound(w, r)
		return
	}
	defer file.Close()
	w.Header().Set("ETag", `"`+info.Version+`"`)
	http.ServeContent(w, r, info.Name, info.ModTime, file)
}
```

## Sources

- [RFC 9110 section 14 Range Requests](https://www.rfc-editor.org/rfc/rfc9110#section-14)
- [RFC 9110 section 13.1.5 If-Range](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.5)

## Related rules

- [Serve private files through short-lived links](https://agentwranglers.se/playbook/api-design/files-and-uploads/signed-downloads) in API design
- [Make large uploads resumable](https://agentwranglers.se/playbook/api-design/files-and-uploads/resumable-uploads) in API design

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/api-design/files-and-uploads/range-downloads">Agent Wranglers API design playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Neutralise formulas in spreadsheet exports](https://agentwranglers.se/playbook/api-design/files-and-uploads/spreadsheet-exports)
- [Files and uploads](https://agentwranglers.se/playbook/api-design/files-and-uploads)
