# CDN Cache and Compression Delivery Contract Gate (`kingii98/cdn-cache-and-compression-delivery-contract-gate`) Actor

Proves that a deploy serves the delivery contract that you agreed. For every URL that you name, the Actor sends header-only transactions: one request for each required compression algorithm, an HTTP-scheme probe and a trailing-slash variant, so four trans

- **URL**: https://apify.com/kingii98/cdn-cache-and-compression-delivery-contract-gate.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 run starteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## CDN Cache and Compression Delivery Contract Gate

Proves that a deploy serves the cache, compression and host-canonicalization
behavior that you agreed. Give the Actor a list of URLs. The Actor sends
header-only requests, reads the answer headers, and gives one PASS, WARN or
FAIL verdict for each URL, plus one machine-readable exit verdict for your CI
job.

The Actor does not crawl. It checks only the URLs that you give. It never reads
a response body, it uses no browser, no proxy and no paid API.

### What the Actor checks

For each URL the Actor sends these transactions:

- one request for each algorithm of your policy, for example
  `Accept-Encoding: br` and then `Accept-Encoding: gzip`;
- one probe on the `http` scheme, for the HTTP-to-HTTPS rule;
- one request to the other trailing-slash form.

The default policy names two algorithms, so one URL costs four transactions.
A policy of three algorithms costs five, and so on.

The Actor then grades these rules:

| Rule | What it reports |
| --- | --- |
| `reachable` | The URL answered. |
| `final_status` | The status code at the end of the redirect chain. |
| `https_redirect` | `http` gives a permanent redirect to `https`. |
| `host_canonical` | The final URL is on the canonical host. |
| `compression_<algorithm>` | One rule for each algorithm of the policy. Each algorithm is graded against its own request. |
| `vary_accept_encoding` | A compressed answer names `Accept-Encoding` in `Vary`. |
| `cache_control` | A `Cache-Control` header is present and is usable. |
| `immutable_max_age` | A fingerprinted asset carries a long `max-age` and the `immutable` directive. |
| `validator` | The answer carries an `ETag` or a `Last-Modified` header. |
| `trailing_slash` | The other trailing-slash form redirects, and does not serve the same page twice. |

A URL takes the most severe verdict of its rules.

### Safety

The Actor refuses every loopback, private, link-local and reserved address. It
follows each redirect hop by hand, and each hop must pass the same control
before the Actor sends the next request. A public host therefore cannot
redirect the Actor to an internal address, and no header of an internal service
reaches the dataset. A refused target is a dataset record with a reason. It
never fails the run, and it is never charged.

### Input

Every field has a default, so a run with empty input succeeds.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `urls` | array of strings | three public `python.org` URLs | 1 to 300 absolute URLs. |
| `requiredEncodings` | array of strings | `["br", "gzip"]` | The algorithms that the CDN must offer. Known values: `br`, `gzip`, `zstd`, `deflate`. The Actor sends one request for each algorithm, and grades each algorithm against its own answer. 1 to 5 algorithms. |
| `minImmutableMaxAgeSeconds` | integer | `31536000` | The smallest `max-age` of a fingerprinted asset. 0 to 31536000. |
| `canonicalHost` | string | `""` | The one host that every URL must end on. An empty value takes the host of the first URL. |
| `trailingSlashForm` | string | `as-given` | `as-given`, `slash` or `no-slash`. |
| `checkHttpToHttps` | boolean | `true` | Send the `http`-scheme probe. |
| `hashedAssetPattern` | string | a build-asset pattern | A regular expression that names a fingerprinted asset. |
| `trackBaseline` | boolean | `true` | Keep the verdict of each URL in the key-value store, and report the delta in the next run. |
| `baselineKey` | string | `""` | An empty value derives the record key from the canonical host. |
| `timeoutSeconds` | integer | `15` | 3 to 60. |
| `maxConcurrency` | integer | `5` | 1 to 10 URLs at the same time. |
| `maxRequests` | integer | `1300` | A hard bound on the run. 10 to 2000. |
| `maxRedirects` | integer | `3` | 0 to 5 hops. Each hop is checked. |

### Output

The dataset holds three record types. The `recordType` field names each one.

**`url`** — one record for each URL:

| Field | Meaning |
| --- | --- |
| `url`, `finalUrl`, `finalStatus` | The URL that you gave, the URL at the end of the chain, and its status. |
| `verdict` | `PASS`, `WARN` or `FAIL`. |
| `reached`, `skipped`, `error` | Whether the URL answered, whether the Actor skipped it, and the reason. |
| `httpProbeStatus`, `httpProbeLocation` | The answer of the `http`-scheme probe. |
| `encodings` | One entry for each requested algorithm, with the algorithm that the server answered with. |
| `brotliEncoding`, `gzipEncoding` | The Brotli and gzip answers, for a quick read. |
| `varyAcceptEncoding`, `vary` | Whether `Vary` names `Accept-Encoding`. |
| `cacheControl`, `cacheDirectives`, `maxAge`, `sMaxAge`, `immutable`, `noStore` | The parsed `Cache-Control` header. |
| `validator` | `etag`, `last-modified` or nothing. |
| `contentType`, `compressible`, `hashedAsset` | The media type, and how the Actor classified it. |
| `slashVariantUrl`, `slashVariantStatus`, `slashVariantLocation` | The answer of the other trailing-slash form. |
| `findings`, `failedRules`, `warnedRules` | Every rule result, and the rules that did not pass. |
| `changeSinceBaseline` | `regressed`, `improved`, `new` or `removed`, against the last run. A URL that is in the baseline, but that does not answer this run, gets `removed`. |
| `checkedAt` | The time of the check. |

**`change`** — one record for each URL whose verdict moved since the baseline.

**`summary`** — one record for each run: `passCount`, `warnCount`, `failCount`,
`urlCount`, `urlsChecked`, `urlsSkipped`, `failuresByRule`, `changeCount`,
`firstRun`, `requestsSent`, `status`, `note`, `policy`, and the two fields that
a CI job reads:

- `exitVerdict` — `PASS`, `WARN` or `FAIL`;
- `gatePass` — `true` when `exitVerdict` is not `FAIL`.

A run that checked nothing reports `exitVerdict: "FAIL"`, so a CI job never
reads a passed gate after a run that verified nothing.

### The run always ends with SUCCEEDED

A failed gate is a business result, not a malfunction. A failed gate, a refused
private target, an unreachable host, a URL above the request bound and a
transport fault are all dataset records plus a status message. The run ends
with `SUCCEEDED`. Read `gatePass` in the summary record to decide whether your
deploy passes. Only an input that cannot be parsed fails the run.

### Billing

The Actor uses the pay-per-event model.

| Event | Unit | Price | When it is charged |
| --- | --- | --- | --- |
| `run-started` | one run | $0.02 | Once for each run, after the input is read and accepted. |
| `url-checked` | one URL that the Actor checks | $0.004 | Once for each URL that the Actor really probed. |

The billing unit is one URL that the Actor checks. A URL that the Actor refused
as a private or reserved target, and a URL that fell above `maxRequests`, are
reported in the dataset but are not charged.

The default maximum total charge for a run is $5.00. That pays for about 1245
URLs. Raise or lower this cap in the run options.

### Use in CI

1. Run the Actor after each deploy with the URL list of the deploy.
2. Read the `summary` record of the dataset.
3. Stop the pipeline when `gatePass` is `false`.

The baseline in the key-value store lets a nightly run report only what moved
since the last accepted run.

# Actor input Schema

## `urls` (type: `array`):

1 to 300 absolute URLs. The Actor does not crawl. It checks only the URLs that you give. Private, loopback and reserved addresses are refused.

## `requiredEncodings` (type: `array`):

The algorithms that the CDN must offer for a compressible body. The Actor sends one request for each algorithm and reads Content-Encoding. An algorithm that the server does not use gives FAIL. Known values: br, gzip, zstd, deflate.

## `minImmutableMaxAgeSeconds` (type: `integer`):

The smallest max-age that a fingerprinted asset must carry. A shorter value gives FAIL. A long enough value without the immutable directive gives WARN.

## `canonicalHost` (type: `string`):

The one host that every URL must end on, for example www.example.com. Leave this empty to take the host of the first URL. A final URL on another host gives FAIL.

## `trailingSlashForm` (type: `string`):

The form that every page URL must use. Choose as-given to report the other form without a policy, slash to require /about/, or no-slash to require /about. The Actor always requests the other form and reports what it answers.

## `checkHttpToHttps` (type: `boolean`):

Send one extra probe on the http scheme. A permanent redirect to https gives PASS, a temporary redirect gives WARN, and a plain 200 answer gives FAIL. Switch this off when port 80 is closed on purpose.

## `hashedAssetPattern` (type: `string`):

A regular expression that matches the path of a build asset that carries a content hash. Only a path that matches is graded by the immutable max-age rule.

## `trackBaseline` (type: `boolean`):

Store the verdict of each URL in the key-value store, and report every change in the next run. The first run writes the baseline and reports no delta.

## `baselineKey` (type: `string`):

Leave this empty to derive the record key from the canonical host. Set it to keep more than one baseline for the same host, for example one for staging and one for production.

## `timeoutSeconds` (type: `integer`):

The Actor stops one request after this time.

## `maxConcurrency` (type: `integer`):

How many URLs the Actor checks at the same time. The four transactions of one URL always run one after the other.

## `maxRequests` (type: `integer`):

A hard bound on the run. Each URL costs one request for each required algorithm, plus the HTTP-scheme probe and the trailing-slash variant, so four requests under the default policy. A URL above the bound is reported as skipped and is not charged.

## `maxRedirects` (type: `integer`):

How many redirect hops the compression requests follow to reach the final URL. Every hop must resolve to a public address, or the Actor stops the chain and reports it. The HTTP-scheme probe and the trailing-slash variant are never followed, because their status code is the result.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org/",
    "https://www.python.org/about/",
    "https://www.python.org/static/img/python-logo.png"
  ],
  "requiredEncodings": [
    "br",
    "gzip"
  ],
  "minImmutableMaxAgeSeconds": 31536000,
  "canonicalHost": "",
  "trailingSlashForm": "as-given",
  "checkHttpToHttps": true,
  "hashedAssetPattern": "[._-][A-Za-z0-9]{8,}\\.(?:js|mjs|cjs|css|map|woff2?|ttf|otf|eot|png|jpe?g|gif|svg|webp|avif|ico)$",
  "trackBaseline": true,
  "baselineKey": "",
  "timeoutSeconds": 15,
  "maxConcurrency": 5,
  "maxRequests": 1300,
  "maxRedirects": 3
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.python.org/",
        "https://www.python.org/about/",
        "https://www.python.org/static/img/python-logo.png"
    ],
    "requiredEncodings": [
        "br",
        "gzip"
    ],
    "minImmutableMaxAgeSeconds": 31536000,
    "canonicalHost": "",
    "trailingSlashForm": "as-given",
    "checkHttpToHttps": true,
    "hashedAssetPattern": "[._-][A-Za-z0-9]{8,}\\.(?:js|mjs|cjs|css|map|woff2?|ttf|otf|eot|png|jpe?g|gif|svg|webp|avif|ico)$",
    "trackBaseline": true,
    "baselineKey": "",
    "timeoutSeconds": 15,
    "maxConcurrency": 5,
    "maxRequests": 1300,
    "maxRedirects": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/cdn-cache-and-compression-delivery-contract-gate").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://www.python.org/",
        "https://www.python.org/about/",
        "https://www.python.org/static/img/python-logo.png",
    ],
    "requiredEncodings": [
        "br",
        "gzip",
    ],
    "minImmutableMaxAgeSeconds": 31536000,
    "canonicalHost": "",
    "trailingSlashForm": "as-given",
    "checkHttpToHttps": True,
    "hashedAssetPattern": "[._-][A-Za-z0-9]{8,}\\.(?:js|mjs|cjs|css|map|woff2?|ttf|otf|eot|png|jpe?g|gif|svg|webp|avif|ico)$",
    "trackBaseline": True,
    "baselineKey": "",
    "timeoutSeconds": 15,
    "maxConcurrency": 5,
    "maxRequests": 1300,
    "maxRedirects": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/cdn-cache-and-compression-delivery-contract-gate").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.python.org/",
    "https://www.python.org/about/",
    "https://www.python.org/static/img/python-logo.png"
  ],
  "requiredEncodings": [
    "br",
    "gzip"
  ],
  "minImmutableMaxAgeSeconds": 31536000,
  "canonicalHost": "",
  "trailingSlashForm": "as-given",
  "checkHttpToHttps": true,
  "hashedAssetPattern": "[._-][A-Za-z0-9]{8,}\\\\.(?:js|mjs|cjs|css|map|woff2?|ttf|otf|eot|png|jpe?g|gif|svg|webp|avif|ico)$",
  "trackBaseline": true,
  "baselineKey": "",
  "timeoutSeconds": 15,
  "maxConcurrency": 5,
  "maxRequests": 1300,
  "maxRedirects": 3
}' |
apify call kingii98/cdn-cache-and-compression-delivery-contract-gate --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/cdn-cache-and-compression-delivery-contract-gate"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/plaAVMfjebOvjkoKD/builds/2D9sB7PSOYtB96rIR/openapi.json
