---
title: Source Provenance and Caching | Tabstack
description: Response headers that show which document a result came from, when it was fetched, and whether it was cached.
---

When an extraction feeds a decision someone may later need to defend, the result alone is not enough. You also need to show which document produced it and whether it was fresh. Every successful response from `/extract/markdown`, `/extract/json` and `/generate/json` carries that information in response headers, so it never changes the shape of your result.

---

## The headers

| Header                     | Example                            | Meaning                                                                                                                    |
| -------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `X-Tabstack-Final-Url`     | `https://www.mozilla.org/`         | The URL the source document was served from, after redirects. Can differ from the `url` you sent.                          |
| `X-Tabstack-Origin-Status` | `200`                              | The HTTP status the origin returned for that document.                                                                     |
| `X-Tabstack-Retrieved-At`  | `2026-11-04T17:52:38Z`             | When the document was retrieved from the origin, as an RFC 3339 UTC timestamp.                                             |
| `X-Tabstack-Checksum`      | `sha256:17f6426d…48fa7`            | SHA-256 of the source document the response was extracted from. See [What the checksum covers](#what-the-checksum-covers). |
| `X-Tabstack-Cache`         | `miss`                             | How caching produced the response. See [Cache status](#cache-status).                                                      |
| `Age`                      | `3`                                | Seconds since the cached content was stored. Sent only when cached content was served.                                     |
| `X-Trace-Id`               | `a47d0ac0e16e96b2613867f231da10b6` | The identifier for this request. Quote it when you contact support. Also sent on error responses.                          |

A response looks like this:

```
HTTP/1.1 200 OK
Content-Type: application/json
X-Tabstack-Cache: miss
X-Tabstack-Checksum: sha256:17f6426dbd14b08ef0bc1fb5c32c8f34461319038841e27e42468534db448fa7
X-Tabstack-Final-Url: https://www.mozilla.org/
X-Tabstack-Origin-Status: 200
X-Tabstack-Retrieved-At: 2026-11-04T17:52:38Z
X-Trace-Id: a47d0ac0e16e96b2613867f231da10b6
```

Here the request was for `http://mozilla.org`. The final URL shows the page that was actually read.

---

## What the checksum covers

The checksum is computed over the exact bytes your result was extracted from, which depends on how the page was fetched:

- **A plain fetch**, such as a PDF or a static page, hashes the file as downloaded. If you hold a copy of the file, its SHA-256 matches the checksum when it is the same document Tabstack read.
- **A page rendered in a browser** (typical at `effort: "max"`) hashes the rendered page. Scripts, ads and timestamps mean a rendered page can differ on every load, so two fetches of the same URL may produce different checksums even when the visible content is unchanged.

Downloading the page yourself later and comparing hashes proves little on its own, because the source can change between fetches. The checksum is most useful recorded alongside the result at the time of extraction: it identifies which bytes produced that result.

---

## Cache status

Tabstack keeps two caches: one for fetched pages and one for finished responses. `X-Tabstack-Cache` tells you which, if either, was used:

| Value       | Meaning                                                                          |
| ----------- | -------------------------------------------------------------------------------- |
| `miss`      | The page was fetched and processed fresh.                                        |
| `fetch-hit` | The page came from the fetch cache, and the response was produced fresh from it. |
| `hit`       | The whole response was served from the response cache.                           |
| `bypass`    | You set `nocache: true`, so no cache was read.                                   |

With `hit` and `fetch-hit`, `X-Tabstack-Retrieved-At` is the time the page was **originally** retrieved, not the time the cached response was served, and the checksum is that of the original document. `Age` tells you how long ago the content was stored.

When you need a record that the page was read at a specific moment, pass `nocache: true`. The response then reports `bypass` and a `Retrieved-At` of the current request. See [Production Reliability](/guides/production-reliability#caching/index.md) for when to bypass the cache.

---

## When a header is missing

A header is left out rather than guessed when its value is unknown. In particular, Tabstack never fills in the final URL with the URL you requested.

- **`X-Tabstack-Final-Url`**: not every fetch method can see the URL a page was ultimately served from.
- **`X-Tabstack-Origin-Status`**: a browser fetch that timed out before the page finished loading, but still had usable content, has no status to report.
- **Any provenance header on a cached response**: responses cached before a header was introduced are served without it until they expire.

Treat each header as optional in your code.

---

## Reading the headers

- [TypeScript](#tab-panel-223)
- [Python](#tab-panel-224)
- [CLI](#tab-panel-225)
- [curl](#tab-panel-226)

```
import Tabstack from "@tabstack/sdk";


interface Provenance {
  finalUrl: string | undefined;
  originStatus: number | undefined;
  retrievedAt: string | undefined;
  checksum: string | undefined;
  cache: string | undefined;
  traceId: string | undefined;
}


// Every header can be absent, so every field may be undefined.
function provenanceFrom(headers: Headers): Provenance {
  const get = (name: string) => headers.get(name) ?? undefined;
  const status = get("X-Tabstack-Origin-Status");
  return {
    finalUrl: get("X-Tabstack-Final-Url"),
    originStatus: status ? Number(status) : undefined,
    retrievedAt: get("X-Tabstack-Retrieved-At"),
    checksum: get("X-Tabstack-Checksum"),
    cache: get("X-Tabstack-Cache"),
    traceId: get("X-Trace-Id"),
  };
}


const client = new Tabstack();


// withResponse() returns the raw response alongside the parsed result.
const { data, response } = await client.extract
  .markdown({ url: "http://mozilla.org" })
  .withResponse();


// Keep the provenance next to the result it describes.
const record = {
  content: data.content,
  provenance: provenanceFrom(response.headers),
};
```

```
from typing import Optional, TypedDict


import httpx
from tabstack import Tabstack




class Provenance(TypedDict):
    final_url: Optional[str]
    origin_status: Optional[int]
    retrieved_at: Optional[str]
    checksum: Optional[str]
    cache: Optional[str]
    trace_id: Optional[str]




# Every header can be absent, so every field may be None.
def provenance_from(headers: httpx.Headers) -> Provenance:
    status = headers.get("X-Tabstack-Origin-Status")
    return {
        "final_url": headers.get("X-Tabstack-Final-Url"),
        "origin_status": int(status) if status else None,
        "retrieved_at": headers.get("X-Tabstack-Retrieved-At"),
        "checksum": headers.get("X-Tabstack-Checksum"),
        "cache": headers.get("X-Tabstack-Cache"),
        "trace_id": headers.get("X-Trace-Id"),
    }




client = Tabstack()


# with_raw_response returns the raw response; parse() gives the result.
response = client.extract.with_raw_response.markdown(url="http://mozilla.org")
data = response.parse()


# Keep the provenance next to the result it describes.
record = {
    "content": data.content,
    "provenance": provenance_from(response.headers),
}
```

Terminal window

```
tabstack extract markdown http://mozilla.org -p
```

`-p` (`--provenance`) prints the provenance to stderr after the result, so piped output is unchanged:

```
provenance
  final url      https://www.mozilla.org/
  origin status  200
  retrieved      2026-11-04T17:52:38Z
  checksum       sha256:17f6426dbd14b08ef0bc1fb5c32c8f34461319038841e27e42468534db448fa7
  cache          miss
  trace id       a47d0ac0e16e96b2613867f231da10b6
```

With `--output json`, it is a single `{"provenance":{...}}` line on stderr instead.

Terminal window

```
curl -si https://api.tabstack.ai/v1/extract/markdown \
  -H "Authorization: Bearer $TABSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"http://mozilla.org"}' | grep -i '^x-tabstack\|^age\|^x-trace-id'
```

Tip

Store the provenance next to each result you keep. The final URL, retrieval time and checksum together identify what was read and when, and the trace ID lets support find the request if a result is ever questioned.

The headers are exposed to browsers through CORS, so code running in a web page can read them too.
