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

# Zero data retention

> Bypass shared caches and keep request and response content out of retained usage logs.

Zero data retention (ZDR) is an opt-in mode on selected Context.dev endpoints. When you set `zdr=enabled` on a request:

* Shared caches are bypassed end-to-end for that request.
* Usage logs record operational metadata only, with no request body, response body, query values, user-agent, or tags.
* Uploaded bytes (parse) and captured screenshots are handled in memory and are never staged in shared object storage.
* The response carries `X-Context-ZDR: true` so you can confirm ZDR was honored.

Use it when your workflow requires these retention controls, such as when processing customer-uploaded documents. Review the [data processing terms](https://context.dev/dpa) for requirements beyond this per-request setting.

<Info>
  Zero data retention is a per-organization entitlement. Contact [support@context.dev](mailto:support@context.dev) to have it enabled on your account before sending ZDR requests.
</Info>

## Supported endpoints

ZDR is only accepted on the endpoints below. Every other endpoint rejects the parameter with `ZDR_NOT_SUPPORTED`.

| Endpoint                      | How to send `zdr` |
| ----------------------------- | ----------------- |
| `GET /v1/web/scrape/markdown` | Query parameter   |
| `GET /v1/web/scrape/html`     | Query parameter   |
| `GET /v1/web/scrape/sitemap`  | Query parameter   |
| `GET /v1/web/screenshot`      | Query parameter   |
| `POST /v1/parse`              | Query parameter   |
| `POST /v1/web/crawl`          | JSON body field   |

Sending `zdr` in the wrong location (for example, in the crawl query string or a scrape body) returns `400 INPUT_VALIDATION_ERROR`.

## Enable ZDR on a request

`zdr` accepts two values: `enabled` and `disabled`. Any other value returns `400 INPUT_VALIDATION_ERROR`. Omitting the parameter is equivalent to `disabled` and behaves exactly like a normal request.

### Query-parameter endpoints

<Tabs>
  <Tab title="Webpage">
    <CodeGroup>
      ```bash cURL theme={null}
      curl --get https://api.context.dev/v1/web/scrape/markdown \
        --header "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
        --data-urlencode "url=https://example.com" \
        --data-urlencode "zdr=enabled"
      ```

      ```typescript TypeScript theme={null}
      import ContextDev from "context.dev";

      const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });

      const response = await client.web.webScrapeMd({
        url: "https://example.com",
        zdr: "enabled",
      });

      console.log(response.markdown);
      ```

      ```python Python theme={null}
      import os
      from context.dev import ContextDev

      client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])

      response = client.web.web_scrape_md(
          url="https://example.com",
          zdr="enabled",
      )

      print(response.markdown)
      ```

      ```ruby Ruby theme={null}
      require "cgi/core"
      require "context_dev"

      client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))

      response = client.web.web_scrape_md(
        url: "https://example.com",
        zdr: "enabled",
      )

      puts response.markdown
      ```

      ```go Go theme={null}
      package main

      import (
          "context"
          "fmt"
          "os"

          contextdev "github.com/context-dot-dev/context-go-sdk/v2"
          "github.com/context-dot-dev/context-go-sdk/v2/option"
      )

      func main() {
          client := contextdev.NewClient(option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")))
          response, err := client.Web.WebScrapeMd(context.Background(), contextdev.WebWebScrapeMdParams{
              URL: "https://example.com",
              Zdr: "enabled",
          })
          if err != nil {
              panic(err)
          }
          fmt.Println(response.Markdown)
      }
      ```

      ```php PHP theme={null}
      <?php

      require __DIR__.'/vendor/autoload.php';

      $client = new ContextDev\Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));

      $response = $client->web->webScrapeMd(
          url: "https://example.com",
          zdr: "enabled",
      );

      echo $response->markdown, PHP_EOL;
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Document">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "https://api.context.dev/v1/parse?extension=pdf&zdr=enabled" \
        -H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
        -H "Content-Type: application/pdf" \
        --data-binary @confidential.pdf
      ```

      ```typescript TypeScript theme={null}
      import { readFile } from "node:fs/promises";
      import ContextDev, { toFile } from "context.dev";

      const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });
      const file = await toFile(await readFile("confidential.pdf"), "confidential.pdf", {
        type: "application/pdf",
      });

      const result = await client.parse.handle(
        file,
        {
          extension: "pdf",
          zdr: "enabled",
        },
        {
          headers: { "Content-Type": "application/pdf" },
        },
      );

      console.log(result.markdown);
      ```

      ```python Python theme={null}
      import os
      from pathlib import Path
      from context.dev import ContextDev

      client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])

      result = client.parse.handle(
          body=Path("confidential.pdf").read_bytes(),
          extension="pdf",
          zdr="enabled",
          extra_headers={"Content-Type": "application/pdf"},
      )

      print(result.markdown)
      ```

      ```ruby Ruby theme={null}
      require "cgi/core"
      require "context_dev"

      client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))

      result = client.parse.handle(
        body: File.binread("confidential.pdf"),
        extension: :pdf,
        zdr: :enabled,
        request_options: {extra_headers: {"Content-Type" => "application/pdf"}},
      )

      puts result.markdown
      ```

      ```go Go theme={null}
      package main

      import (
          "context"
          "fmt"
          "os"

          contextdev "github.com/context-dot-dev/context-go-sdk/v2"
          "github.com/context-dot-dev/context-go-sdk/v2/option"
      )

      func main() {
          client := contextdev.NewClient(
              option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")),
          )

          file, err := os.Open("confidential.pdf")
          if err != nil {
              panic(err)
          }
          defer file.Close()

          // SDK 2.14.0 needs explicit query options for Parse.
          result, err := client.Parse.Handle(
              context.Background(),
              file,
              contextdev.ParseHandleParams{},
              option.WithQuery("extension", "pdf"),
              option.WithQuery("zdr", "enabled"),
              option.WithHeader("Content-Type", "application/pdf"),
          )
          if err != nil {
              panic(err)
          }

          fmt.Println(result.Markdown)
      }
      ```

      ```php PHP theme={null}
      <?php

      require __DIR__.'/vendor/autoload.php';

      use ContextDev\Client;

      $client = new Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));

      $bytes = file_get_contents('confidential.pdf');
      if ($bytes === false) {
          throw new RuntimeException('Could not read confidential.pdf');
      }

      // Use the SDK's request method: the 2.14.0 Parse helper drops the body.
      $raw = $client->request(
          method: 'post',
          path: 'parse',
          query: ['extension' => 'pdf', 'zdr' => 'enabled'],
          headers: ['Content-Type' => 'application/pdf'],
          body: $bytes,
      );

      $result = json_decode((string) $raw->getBody(), true, flags: JSON_THROW_ON_ERROR);
      echo $result['markdown'], PHP_EOL;
      ```
    </CodeGroup>
  </Tab>
</Tabs>

The PHP Parse example uses the SDK's low-level request to send raw file bytes. The Go example passes query options explicitly because SDK 2.14.0 does not forward Parse parameters.

### Body-parameter endpoint (crawl)

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.context.dev/v1/web/crawl \
    --request POST \
    --header "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
    "url": "https://example.com",
    "zdr": "enabled"
  }'
  ```

  ```typescript TypeScript theme={null}
  import ContextDev from "context.dev";

  const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });

  const response = await client.web.webCrawlMd({
    url: "https://example.com",
    zdr: "enabled",
  });

  console.log(response);
  ```

  ```python Python theme={null}
  import os
  from context.dev import ContextDev

  client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])

  response = client.web.web_crawl_md(
      url="https://example.com",
      zdr="enabled",
  )

  print(response)
  ```

  ```ruby Ruby theme={null}
  require "cgi/core"
  require "context_dev"

  client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))

  response = client.web.web_crawl_md(
    url: "https://example.com",
    zdr: "enabled",
  )

  pp response
  ```

  ```go Go theme={null}
  package main

  import (
      "context"
      "fmt"
      "os"

      contextdev "github.com/context-dot-dev/context-go-sdk/v2"
      "github.com/context-dot-dev/context-go-sdk/v2/option"
  )

  func main() {
      client := contextdev.NewClient(option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")))
      response, err := client.Web.WebCrawlMd(context.Background(), contextdev.WebWebCrawlMdParams{
          URL: "https://example.com",
          Zdr: "enabled",
      })
      if err != nil {
          panic(err)
      }
      fmt.Println(response)
  }
  ```

  ```php PHP theme={null}
  <?php

  require __DIR__.'/vendor/autoload.php';

  $client = new ContextDev\Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));

  $response = $client->web->webCrawlMd(
      url: "https://example.com",
      zdr: "enabled",
  );

  print_r($response);
  ```
</CodeGroup>

## Confirm ZDR was honored

Successful ZDR responses include the CORS-exposed response header:

```
X-Context-ZDR: true
```

If the header is missing, do not treat the response as confirmation of ZDR. Check the request parameter and status; the request may have used standard mode or failed before ZDR took effect.

## Trade-offs

Because ZDR skips shared caches and richer processing paths, expect:

* **No shared cache reuse.** Repeated ZDR requests cannot reuse a shared cached result, so they may take longer than standard requests.
* **No search-assisted sitemap discovery.** `GET /v1/web/scrape/sitemap` under ZDR relies on direct crawling only.
* **No usage tags.** Request `tags` are dropped from usage logs. Track ZDR usage in your own systems if you need per-workload attribution.

Pricing and rate limits are unchanged.

## Errors

| HTTP | `error_code`             | Meaning                                                                                                        |
| ---- | ------------------------ | -------------------------------------------------------------------------------------------------------------- |
| 400  | `ZDR_NOT_SUPPORTED`      | The endpoint doesn't accept `zdr`. Use a supported endpoint from the table above.                              |
| 400  | `INPUT_VALIDATION_ERROR` | `zdr` was sent in the wrong location, with an invalid value, or in both query and body.                        |
| 403  | `ZDR_NOT_ENABLED`        | Your organization doesn't have the ZDR entitlement. Contact [support@context.dev](mailto:support@context.dev). |

## Related

<CardGroup cols={2}>
  <Card title="Request tags" icon="tags" href="/optimization/tag-requests">
    Understand usage attribution and why tags are dropped under ZDR.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/optimization/rate-limits">
    Pace ZDR requests within the standard rate limits.
  </Card>
</CardGroup>
