# How should a resumable upload API recover when client and server offsets disagree?

> Treat the server’s reported offset as the only authoritative upload state.

Canonical URL: https://www.devobs.io/articles/qa-resumable-upload-offset/
By: Sofia Reyes
Published: 2023-10-30T12:15:43.378Z
Updated: 2026-09-06T10:18:15.722Z
Section: Architecture

Treat the server’s confirmed offset as the protocol state. If the client and server disagree, the client should stop guessing, issue `HEAD` for the upload resource, read the current `Upload-Offset`, and resume exactly there. For an append-at-offset contract, the server should reject any `PATCH` whose starting offset does not match current state with `409 Conflict`, so retries after disconnects do not duplicate bytes or silently skip data ([tus resumable upload protocol](https://tus.io/protocols/resumable-upload), [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html)).

## Why should the server offset be authoritative?

After a timeout or broken connection, the client cannot know whether the last chunk was committed. HTTP does not carry application state between requests, and a missing response leaves the client without confirmation of the outcome ([RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html)). In practice, that means the upload resource itself has to tell the client where to continue.

For each upload, compare the request’s expected offset with the stored offset and append bytes only if they match, as one guarded operation. A separate preliminary check cannot prevent another request from advancing the offset before the append.

## What should happen after an interrupted response?

Assume the client has uploaded 70 bytes, sends the next 30-byte chunk, and loses the response. Two states are now plausible: the server is still at 70, or it advanced to 100 before the response was lost. The client should not resend the 30 bytes blindly.

The recovery path is:

- `HEAD` the upload URL
- read `Upload-Offset`
- send the next `PATCH` starting exactly at that offset

In tus, `HEAD` discovers the resume position. A subsequent `PATCH` must supply that position through `Upload-Offset`, and the server checks it against the upload's current state before accepting the append ([tus resumable upload protocol](https://tus.io/protocols/resumable-upload)).

## How should the API respond to an offset mismatch?

Treat a mismatch as a synchronization failure, not as a hint to merge overlapping ranges. Under tus, the server rejects this request with `409 Conflict` when its current offset differs from the client's expected position ([tus resumable upload protocol](https://tus.io/protocols/resumable-upload)). That is the right contract even if you are not implementing tus verbatim.

A compact checklist:

- `HEAD` returns the current committed offset
- `HEAD` includes `Cache-Control: no-store`
- `PATCH` requires one explicit starting offset
- server compares that offset immediately before append
- on mismatch, return `409 Conflict` and do not mutate the upload
- validate chunk framing and any optional checksum before advancing offset
- only advance the stored offset after the bytes are accepted for that upload resource

## Worked example?

Client state says 70. It sends 30 bytes with `Upload-Offset: 70`. The network drops before the response arrives.

Recovery:

1. Client sends `HEAD /uploads/abc`
2. Server replies `Upload-Offset: 100`
3. Client resumes from 100, not 70

If the server instead reports 70, the client retransmits from 70. If the client ignores the probe and sends another `PATCH` with `Upload-Offset: 70` after the server already moved to 100, the correct response is `409 Conflict` ([tus resumable upload protocol](https://tus.io/protocols/resumable-upload)).

This guidance applies to append-only resumable uploads. If your API allows sparse out-of-order writes or separate multipart assembly, you need a different reconciliation model than a single monotonic offset.

## Follow-up: should the client ever trust its local offset after a timeout?

No. Local offset is only a resume hint. After any ambiguous failure, probe the resource and trust the server’s reported offset.

## Follow-up: should the server accept overlapping retries to be forgiving?

Usually no. Strict offset matching keeps retries deterministic and prevents duplicate bytes from becoming valid state.

Next step: write your upload contract so `HEAD` is the recovery probe and offset equality is enforced immediately before append, then add a test that drops the response after a committed chunk and verifies resume behavior.

Reviewed: 2026-09-05

## Source references

- <https://tus.io/protocols/resumable-upload>
- <https://www.rfc-editor.org/rfc/rfc9110.html>
