GET/api/v2/http-headers

HTTP security headers API

Fetch a URL like a browser, follow its redirects, and grade the final answer's security headers with Mozilla HTTP Observatory's tests.

GEThttps://dnsrobot.netBase URL/api/v2Version/http-headersEndpoint?url=&ua=Parameters
200 OKRecorded 2026-10-25. Send it to run the request live.
{
  "query": {
    "url": "https://github.com/",
    "ua": "chrome"
  },
  "finalUrl": "https://github.com/",
  "finalStatus": 200,
  "redirects": 0,
  "httpVersion": "1.1",
  "http2": true,
  "http3": false,
  "headersExact": true,
  "headerCount": 20,
  "headers": [
    {
      "name": "Date",
      "value": "Sun, 25 Oct 2026 08:13:40 GMT"
    },
    {
      "name": "Content-Type",
      "value": "text/html; charset=utf-8"
    },
    {
      "name": "Vary",
      "value": "X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With, X-GitHub-Client-Version, Accept-Language, Sec-Fetch-Site,Accept-Encoding, Accept, X-Requested-With"
    },
    {
      "name": "content-language",
      "value": "en-US"
    },
    {
      "name": "ETag",
      "value": "W/\"73f87ca5b67703b238ecb2e0d277aac4\""
    },
    {
      "name": "Cache-Control",
      "value": "max-age=0, private, must-revalidate"
    },
    {
      "name": "Strict-Transport-Security",
      "value": "max-age=31536000; includeSubdomains; preload"
    },
    {
      "name": "X-Frame-Options",
      "value": "deny"
    },
    {
      "name": "X-Content-Type-Options",
      "value": "nosniff"
    },
    {
      "name": "X-XSS-Protection",
      "value": "0"
    },
    {
      "name": "Referrer-Policy",
      "value": "origin-when-cross-origin, strict-origin-when-cross-origin"
    },
    {
      "name": "Content-Security-Policy",
      "value": "default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com github.githubassets.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net productionresultssa4.blob.core.windows.net productionresultssa5.blob.core.windows.net productionresultssa6.blob.core.windows.net productionresultssa7.blob.core.windows.net productionresultssa8.blob.core.windows.net productionresultssa9.blob.core.windows.net productionresultssa10.blob.core.windows.net productionresultssa11.blob.core.windows.net productionresultssa12.blob.core.windows.net productionresultssa13.blob.core.windows.net productionresultssa14.blob.core.windows.net productionresultssa15.blob.core.windows.net productionresultssa16.blob.core.windows.net productionresultssa17.blob.core.windows.net productionresultssa18.blob.core.windows.net productionresultssa19.blob.core.windows.net github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com wss://production-copilot-host.webpubsub.azure.com wss://production-copilot-host-centralus.webpubsub.azure.com wss://production-copilot-host-centralus-02.webpubsub.azure.com wss://production-copilot-host-westus.webpubsub.azure.com api.github.com/cmc_internal/api/ edge.fullstory.com rs.fullstory.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com www.youtube-nocookie.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com user-images.githubusercontent.com private-user-images.githubusercontent.com opengraph.githubassets.com repository-images.githubusercontent.com marketplace-screenshots.githubusercontent.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com explore-feed.github.com *.googleusercontent.com objects-origin.githubusercontent.com *.githubusercontent.com images.ctfassets.net/8aevphvgewt8/; manifest-src 'self'; media-src github.com user-images.githubusercontent.com secured-user-images.githubusercontent.com private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com assets.ctfassets.net/8aevphvgewt8/ videos.ctfassets.net/8aevphvgewt8/; script-src github.githubassets.com 'sha256-6AKuI+cDd1MoUL1u4eqrA9PXmnPAkOB4h7421hPUUm0='; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/"
    },
    {
      "name": "Server",
      "value": "github.com"
    },
    {
      "name": "Accept-Ranges",
      "value": "bytes"
    },
    {
      "name": "Set-Cookie",
      "value": "_gh_sess=‹…›; path=/; HttpOnly; secure; SameSite=Lax"
    },
    {
      "name": "Set-Cookie",
      "value": "_octo=‹…›; expires=Mon, 25 Oct 2027 08:13:49 GMT; domain=.github.com; path=/; secure; SameSite=Lax"
    },
    {
      "name": "Set-Cookie",
      "value": "logged_in=‹…›; expires=Mon, 25 Oct 2027 08:13:49 GMT; domain=.github.com; path=/; HttpOnly; secure; SameSite=Lax"
    },
    {
      "name": "X-GitHub-Request-Id",
      "value": "2F4C:10C7C1:118181:1A2B78:6ADDBA3D"
    },
    {
      "name": "x-github-edge-region",
      "value": "centralindia"
    },
    {
      "name": "Transfer-Encoding",
      "value": "chunked"
    }
  ],
  "grade": {
    "letter": "A+",
    "score": 115,
    "graded": true,
    "notGradedReason": null,
    "provisional": [],
    "testsPassed": 11,
    "testsFailed": 1,
    "basis": "mdn-http-observatory 1.7.1 (algorithm 6)"
  },
  "tests": [
    {
      "id": "content-security-policy",
      "name": "Content-Security-Policy",
      "result": "csp-implemented-with-unsafe-inline-in-style-src-only",
      "summary": "set; 'unsafe-inline' only in style-src",
      "pass": true,
      "modifier": 0,
      "evidence": "default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com github.githubassets.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net productionresultssa4.blob…",
      "fix": "Remove `'unsafe-inline'` from style-src too, for the last 5 points."
    },
    {
      "id": "cookies",
      "name": "Cookies",
      "result": "cookies-secure-with-httponly-sessions-and-samesite",
      "summary": "all Secure, sessions HttpOnly, SameSite set",
      "pass": true,
      "modifier": 5,
      "evidence": "_gh_sess; Secure; HttpOnly; SameSite=lax | _octo; Secure; SameSite=lax | logged_in; Secure; HttpOnly; SameSite=lax",
      "fix": null
    },
    {
      "id": "cross-origin-embedder-policy",
      "name": "Cross-Origin-Embedder-Policy",
      "result": "coep-not-implemented",
      "summary": "not sent",
      "pass": true,
      "modifier": 0,
      "evidence": null,
      "fix": "Send `Cross-Origin-Embedder-Policy: require-corp` (or credentialless) once embedded content allows it."
    },
    {
      "id": "cross-origin-opener-policy",
      "name": "Cross-Origin-Opener-Policy",
      "result": "coop-not-implemented",
      "summary": "not sent",
      "pass": true,
      "modifier": 0,
      "evidence": null,
      "fix": "Send `Cross-Origin-Opener-Policy: same-origin`."
    },
    {
      "id": "cross-origin-resource-sharing",
      "name": "Cross-Origin Resource Sharing",
      "result": "cross-origin-resource-sharing-not-implemented",
      "summary": "no CORS headers",
      "pass": true,
      "modifier": 0,
      "evidence": null,
      "fix": null
    },
    {
      "id": "redirection",
      "name": "Redirection to HTTPS",
      "result": "redirection-all-redirects-preloaded",
      "summary": "every hop on the HSTS preload list",
      "pass": true,
      "modifier": 0,
      "evidence": "http://github.com/ → https://github.com/",
      "fix": null
    },
    {
      "id": "referrer-policy",
      "name": "Referrer-Policy",
      "result": "referrer-policy-private",
      "summary": "a private value",
      "pass": true,
      "modifier": 5,
      "evidence": "origin-when-cross-origin, strict-origin-when-cross-origin",
      "fix": null
    },
    {
      "id": "strict-transport-security",
      "name": "Strict-Transport-Security",
      "result": "hsts-preloaded",
      "summary": "on the preload list",
      "pass": true,
      "modifier": 5,
      "evidence": "max-age=31536000; includeSubdomains; preload",
      "fix": null
    },
    {
      "id": "subresource-integrity",
      "name": "Subresource Integrity",
      "result": "sri-not-implemented-but-external-scripts-loaded-securely",
      "summary": "external scripts over HTTPS, no integrity=",
      "pass": false,
      "modifier": -5,
      "evidence": "https://github.githubassets.com/assets/high-contrast-cookie-141c17800ca6ba23.js | https://github.githubassets.com/assets/environment-9c4682c5715f3b80.js | https://github.githubassets.com/assets/github-elements-3b31060462951f7e.js | https://github.githubassets.com/assets/element-registry-8f2242d8ab39e777.js | https://github.githubassets.com/assets/behaviors-aae3017816009922.js | https://github.githubassets.com/assets/marketing-essentials-15557679ec4049c8.js | https://github.githubassets.com/assets/landing-pages-b8f11f093a587ea8.js | https://github.githubassets.com/assets/global-banner-disable-750ad87f36b8e45f.js",
      "fix": "Add `integrity=\"sha384-…\" crossorigin=\"anonymous\"` to each external script: a page change, not a header."
    },
    {
      "id": "x-content-type-options",
      "name": "X-Content-Type-Options",
      "result": "x-content-type-options-nosniff",
      "summary": "nosniff",
      "pass": true,
      "modifier": 0,
      "evidence": "nosniff",
      "fix": null
    },
    {
      "id": "x-frame-options",
      "name": "X-Frame-Options",
      "result": "x-frame-options-implemented-via-csp",
      "summary": "via CSP frame-ancestors",
      "pass": true,
      "modifier": 5,
      "evidence": "frame-ancestors 'none'",
      "fix": null
    },
    {
      "id": "cross-origin-resource-policy",
      "name": "Cross-Origin-Resource-Policy",
      "result": "corp-not-implemented",
      "summary": "not sent",
      "pass": true,
      "modifier": 0,
      "evidence": null,
      "fix": "Send `Cross-Origin-Resource-Policy: same-origin` (same-site if your subdomains embed it)."
    }
  ],
  "hstsPreload": {
    "status": "preloaded",
    "preloadedBy": null
  },
  "software": [
    {
      "id": "github",
      "name": "GitHub",
      "version": null,
      "header": "server"
    }
  ],
  "cookies": [
    {
      "name": "_gh_sess",
      "host": "github.com",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax",
      "partitioned": false,
      "path": "/",
      "domain": null,
      "maxAge": null,
      "valueLength": 354,
      "problems": []
    },
    {
      "name": "_octo",
      "host": "github.com",
      "secure": true,
      "httpOnly": false,
      "sameSite": "lax",
      "partitioned": false,
      "path": "/",
      "domain": "github.com",
      "maxAge": 31535999,
      "valueLength": 26,
      "problems": []
    },
    {
      "name": "logged_in",
      "host": "github.com",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax",
      "partitioned": false,
      "path": "/",
      "domain": "github.com",
      "maxAge": 31535999,
      "valueLength": 2,
      "problems": []
    }
  ],
  "issues": [
    {
      "severity": "warning",
      "code": "sri_not_implemented_but_external_scripts_loaded_securely",
      "message": "Subresource Integrity: external scripts over HTTPS, no integrity= (-5). Add `integrity=\"sha384-…\" crossorigin=\"anonymous\"` to each external script: a page change, not a header."
    },
    {
      "severity": "info",
      "code": "permissions_policy_missing",
      "message": "No Permissions-Policy. Not scored; only Chromium browsers read it, where it can switch off the camera, microphone and location for embedded content."
    }
  ],
  "tookMs": 963
}

How it works

Requests the URL from our server over our own connection (so the headers are exactly as sent: order, case and duplicates), follows up to 20 redirects, and returns the final answer's status and every header line. Cookie values are never read or stored: only their names and attributes.

The grade reproduces Mozilla HTTP Observatory (its v1.7.1 code, algorithm 6): the same 12 tests (CSP, cookies, CORS, COOP/COEP/CORP, redirection, Referrer-Policy, HSTS, SRI, X-Content-Type-Options, X-Frame-Options), the same points and the same letter, read from the same two sessions (the URL's http:// and https:// forms) and the HSTS preload list. Each test comes with its evidence and a fix; deprecated or revealing headers (X-XSS-Protection, Server versions, X-Powered-By) are notes in issues.

Honest limits: sites on Cloudflare's network are read through Cloudflare (header names arrive lower-case: headersExact: false). When the HTTPS answer couldn't be read on our side, the affected tests say unknown and the grade is provisional, never failed. A bot wall is reported as such, not as a broken site.

Answers kept: The same run serves both endpoints and the HTTP Header Checker and Redirect Checker pages: a finished check is kept 2 minutes (30 seconds when a part ran out of time) per URL and user agent. A run that ended on a bot wall or whose first URL got no answer isn't kept.

Query parameters

  • urlstringrequired
    A web address: a full URL (https://github.com/about) or a bare name (github.com, checked as https://). Private and reserved addresses are refused.
  • uastringoptionaldefault chrome
    The user agent to send: chrome (Chrome on Windows, the default), iphone (Safari on iOS), googlebot, bingbot or dnsrobot (ours). Sites answer bots and phones differently.

    one of chrome, iphone, googlebot, bingbot, dnsrobot

Response fields

200 with this JSON object, plus tookMs, and incomplete with warnings when a source failed. Fields marked optional appear only when they apply.

  • queryobjectalways
    What was looked up, normalised (lower case, no trailing dot, IDNs as Punycode).
    • urlstringalways
      The URL checked: scheme added when missing (https), host lower case and Punycode, default port and #fragment dropped.
    • uastringalways
      The user agent the check sent.

      one of "chrome", "iphone", "googlebot", "bingbot", "dnsrobot"

  • finalUrlstring or nullalways
    The URL whose answer is shown, after following redirects; null when nothing answered.
  • finalStatusinteger or nullalways
    Its HTTP status.
  • redirectsintegeralways
    Redirects followed to get there.
  • httpVersionstring or nullalways
    From the status line (1.1); null through Cloudflare's network.
  • http2boolean or nullalways
    The server offers HTTP/2 (ALPN h2); null when not known.
  • http3boolean or nullalways
    HTTP/3 is advertised (Alt-Svc).
  • headersExactboolean or nullalways
    true: the headers are exactly as sent (order, case, duplicates); false: read through Cloudflare's network, names lower-cased and repeats joined.
  • headerCountintegeralways
    Header lines in the final answer.
  • headersarray of objectsalways
    The final answer's header lines, at most 100, values cut at 4,096 characters. Cookie values are replaced by ‹…› (never read, never stored).
    • namestringalways
      Its name.
    • valuestringalways
      Its value.
  • gradeobject or nullalways
    The security grade, computed with Mozilla HTTP Observatory's tests and scoring (its v1.7.1 code). Null when the tests didn't run (nothing answered, or the check ran out of time).
    Show 8 fields
    • letterstring or nullalways
      A+ to F; null when the answer isn't graded.

      one of "A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "F", null

    • scoreinteger or nullalways
      Observatory's score (starts at 100; bonuses can take it past 100).
    • gradedbooleanalways
      Observatory grades this answer (only 2xx, 3xx, 401 and 403 are).
    • notGradedReasonstring or nullalways
      Why not: status (not a graded status), unreachable, or unknown (the HTTPS answer couldn't be read this time, on our side).

      one of "status", "unreachable", "unknown", null

    • provisionalarray of stringsalways
      Why the grade may still change: preload (hstspreload.org didn't answer), page (the page wasn't read whole), session (a test's answer couldn't be read).
    • testsPassedintegeralways
      Tests passed.
    • testsFailedintegeralways
      Tests failed.
    • basisstringalways
      The method and version matched.
  • testsarray of objectsalways
    Each Observatory test with its result.
    Show 8 fields
    • idstringalways
      The test.

      one of "content-security-policy", "cookies", "cross-origin-embedder-policy", "cross-origin-opener-policy", "cross-origin-resource-sharing", "redirection", "referrer-policy", "strict-transport-security", "subresource-integrity", "x-content-type-options", "x-frame-options", "cross-origin-resource-policy"

    • namestringalways
      Its name.
    • resultstringalways
      Observatory's own result id, e.g. hsts-implemented-max-age-at-least-six-months; unknown when the answer it reads couldn't be had on our side (scored 0).
    • summarystringalways
      The result in words.
    • passbooleanalways
      Observatory's pass/fail.
    • modifierintegeralways
      Points added to or taken from the score.
    • evidencestring or nullalways
      The header value (or cookie names, script URLs) the result rests on, up to 1 KB.
    • fixstring or nullalways
      How to fix it, in a sentence; null when nothing to fix.
  • hstsPreloadobject or nullalways
    The host's status on Chrome's HSTS preload list (hstspreload.org).
    • statusstringalways
      Status.

      one of "preloaded", "pending", "eligible", "no", "unknown"

    • preloadedBystring or nullalways
      The parent whose entry covers this host.
  • softwarearray of objectsalways
    Server and CDN software the headers name.
    Show 4 fields
    • idstringalways
      Stable id, e.g. nginx, cloudflare, github.
    • namestringalways
      Display name.
    • versionstring or nullalways
      The version the header gives away.
    • headerstringalways
      The header that named it.
  • cookiesarray of objectsalways
    Cookies set along the chain (values never read), at most 30.
    Show 11 fields
    • namestringalways
      Its name.
    • hoststringalways
      The host that set it.
    • securebooleanalways
      Secure.
    • httpOnlybooleanalways
      HttpOnly.
    • sameSitestringalways
      SameSite as sent.

      one of "absent", "lax", "strict", "none", "invalid"

    • partitionedbooleanalways
      Partitioned (CHIPS).
    • pathstring or nullalways
      Path.
    • domainstring or nullalways
      Domain.
    • maxAgeinteger or nullalways
      Lifetime in seconds (Max-Age, else Expires; capped at 400 days as browsers do); null for a session cookie.
    • valueLengthintegeralways
      Length of its value in bytes.
    • problemsarray of stringsalways
      Problems browsers act on: prefix-secure, prefix-host, samesite-none-insecure, partitioned-insecure, domain-mismatch, bad-name, long-attribute, lifetime-capped.
  • issuesarray of objectsalways
    Problems found in the target's configuration, worst first. Empty when there are none.
    • severitystringalways
      error breaks something, warning risks it, info is worth knowing.

      one of "error", "warning", "info"

    • codestringalways
      Stable machine-readable code.
    • messagestringalways
      The problem and what to do about it, in plain English.

Errors

  • 422 target_not_allowed The URL points at a private or reserved address, port 25, or our own API.
  • 429 rate_limited Over your 20 a minute, or the target site's 40 a minute from everyone (the message says which).
  • 503 unavailable The checker's rate limiter isn't reachable: it fails closed rather than run a check uncounted.

Plus the errors every endpoint can return: 400 for a missing or unreadable parameter, 429 past the limit, 503 when every source failed.

Code samples

curl -G "https://dnsrobot.net/api/v2/http-headers" \
  --data-urlencode "url=https://github.com"
const params = new URLSearchParams({ url: "https://github.com" });
const res = await fetch(`https://dnsrobot.net/api/v2/http-headers?${params}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.grade);
import requests

r = requests.get(
    "https://dnsrobot.net/api/v2/http-headers",
    params={"url": "https://github.com"},
    timeout=30,
)
data = r.json()
if not r.ok:
    raise RuntimeError(data["error"]["message"])
print(data["grade"])
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

func main() {
	q := url.Values{"url": {"https://github.com"}}
	res, err := http.Get("https://dnsrobot.net/api/v2/http-headers?" + q.Encode())
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var data map[string]any
	if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
		panic(err)
	}
	if res.StatusCode != http.StatusOK {
		panic(data["error"])
	}
	fmt.Println(data["grade"])
}
<?php
$query = http_build_query(["url" => "https://github.com"]);
$context = stream_context_create(["http" => ["ignore_errors" => true, "timeout" => 30]]);
$data = json_decode(file_get_contents("https://dnsrobot.net/api/v2/http-headers?" . $query, false, $context), true);
if (isset($data["error"])) {
    throw new RuntimeException($data["error"]["message"]);
}
print_r($data["grade"]);
require "net/http"
require "json"

uri = URI("https://dnsrobot.net/api/v2/http-headers")
uri.query = URI.encode_www_form(url: "https://github.com")
data = JSON.parse(Net::HTTP.get(uri))
raise data["error"]["message"] if data["error"]
puts data["grade"]
// Cargo.toml: reqwest = { version = "0.13", features = ["blocking", "json"] }, serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let res = reqwest::blocking::get("https://dnsrobot.net/api/v2/http-headers?url=https%3A%2F%2Fgithub.com")?;
    let ok = res.status().is_success();
    let data: serde_json::Value = res.json()?;
    if !ok {
        return Err(data["error"]["message"].to_string().into());
    }
    println!("{}", data["grade"]);
    Ok(())
}
using System.Text.Json;

using var http = new HttpClient();
var res = await http.GetAsync("https://dnsrobot.net/api/v2/http-headers?url=https%3A%2F%2Fgithub.com");
var data = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
if (!res.IsSuccessStatusCode)
    throw new Exception(data.GetProperty("error").GetProperty("message").GetString());
Console.WriteLine(data.GetProperty("grade"));

github.com: A+ with a strict CSP

GET /api/v2/http-headers?url=https%3A%2F%2Fgithub.com
200 OKRecorded 2026-10-25
{
  "query": {
    "url": "https://github.com/",
    "ua": "chrome"
  },
  "finalUrl": "https://github.com/",
  "finalStatus": 200,
  "redirects": 0,
  "httpVersion": "1.1",
  "http2": true,
  "http3": false,
  "headersExact": true,
  "headerCount": 20,
  "headers": [
    {
      "name": "Date",
      "value": "Sun, 25 Oct 2026 08:13:40 GMT"
    },
    {
      "name": "Content-Type",
      "value": "text/html; charset=utf-8"
    },
    {
      "name": "Vary",
      "value": "X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With, X-GitHub-Client-Version, A…"
    },
    // … 17 more items
  ],
  "grade": {
    "letter": "A+",
    "score": 115,
    "graded": true,
    "notGradedReason": null,
    "provisional": [],
    "testsPassed": 11,
    "testsFailed": 1,
    "basis": "mdn-http-observatory 1.7.1 (algorithm 6)"
  },
  "tests": [
    {
      "id": "content-security-policy",
      "name": "Content-Security-Policy",
      "result": "csp-implemented-with-unsafe-inline-in-style-src-only",
      "summary": "set; 'unsafe-inline' only in style-src",
      "pass": true,
      "modifier": 0,
      "evidence": "default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/wor…",
      "fix": "Remove `'unsafe-inline'` from style-src too, for the last 5 points."
    },
    {
      "id": "cookies",
      "name": "Cookies",
      "result": "cookies-secure-with-httponly-sessions-and-samesite",
      "summary": "all Secure, sessions HttpOnly, SameSite set",
      "pass": true,
      "modifier": 5,
      "evidence": "_gh_sess; Secure; HttpOnly; SameSite=lax | _octo; Secure; SameSite=lax | logged_in; Secure; Http…",
      "fix": null
    },
    {
      "id": "cross-origin-embedder-policy",
      "name": "Cross-Origin-Embedder-Policy",
      "result": "coep-not-implemented",
      "summary": "not sent",
      "pass": true,
      "modifier": 0,
      "evidence": null,
      "fix": "Send `Cross-Origin-Embedder-Policy: require-corp` (or credentialless) once embedded content allo…"
    },
    // … 9 more items
  ],
  "hstsPreload": {
    "status": "preloaded",
    "preloadedBy": null
  },
  "software": [
    {
      "id": "github",
      "name": "GitHub",
      "version": null,
      "header": "server"
    }
  ],
  "cookies": [
    {
      "name": "_gh_sess",
      "host": "github.com",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax",
      "partitioned": false,
      "path": "/",
      "domain": null,
      "maxAge": null,
      "valueLength": 354,
      "problems": []
    },
    {
      "name": "_octo",
      "host": "github.com",
      "secure": true,
      "httpOnly": false,
      "sameSite": "lax",
      "partitioned": false,
      "path": "/",
      "domain": "github.com",
      "maxAge": 31535999,
      "valueLength": 26,
      "problems": []
    },
    {
      "name": "logged_in",
      "host": "github.com",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax",
      "partitioned": false,
      "path": "/",
      "domain": "github.com",
      "maxAge": 31535999,
      "valueLength": 2,
      "problems": []
    }
  ],
  "issues": [
    {
      "severity": "warning",
      "code": "sri_not_implemented_but_external_scripts_loaded_securely",
      "message": "Subresource Integrity: external scripts over HTTPS, no integrity= (-5). Add `integrity=\"sha384-……"
    },
    {
      "severity": "info",
      "code": "permissions_policy_missing",
      "message": "No Permissions-Policy. Not scored; only Chromium browsers read it, where it can switch off the c…"
    }
  ],
  "tookMs": 963
}

Shortened for the page: long lists show their first items.

wikipedia.org

GET /api/v2/http-headers?url=https%3A%2F%2Fwww.wikipedia.org
200 OKRecorded 2026-10-25
{
  "query": {
    "url": "https://www.wikipedia.org/",
    "ua": "chrome"
  },
  "finalUrl": "https://www.wikipedia.org/",
  "finalStatus": 200,
  "redirects": 0,
  "httpVersion": "1.1",
  "http2": true,
  "http3": false,
  "headersExact": true,
  "headerCount": 23,
  "headers": [
    {
      "name": "date",
      "value": "Sun, 25 Oct 2026 05:07:59 GMT"
    },
    {
      "name": "cache-control",
      "value": "s-maxage=86400, must-revalidate, max-age=3600"
    },
    {
      "name": "server",
      "value": "ATS/9.2.15"
    },
    // … 20 more items
  ],
  "grade": {
    "letter": "C",
    "score": 50,
    "graded": true,
    "notGradedReason": null,
    "provisional": [],
    "testsPassed": 9,
    "testsFailed": 3,
    "basis": "mdn-http-observatory 1.7.1 (algorithm 6)"
  },
  "tests": [
    {
      "id": "content-security-policy",
      "name": "Content-Security-Policy",
      "result": "csp-not-implemented",
      "summary": "not sent",
      "pass": false,
      "modifier": -25,
      "evidence": null,
      "fix": "Send a Content-Security-Policy. Start it as Report-Only, read the reports, then enforce it."
    },
    {
      "id": "cookies",
      "name": "Cookies",
      "result": "cookies-secure-with-httponly-sessions",
      "summary": "all Secure, sessions HttpOnly",
      "pass": true,
      "modifier": 0,
      "evidence": "WMF-Last-Access; Secure; HttpOnly | WMF-Last-Access-Global; Secure; HttpOnly | GeoIP; Secure | N…",
      "fix": "Add `SameSite=Lax` (or Strict) to every cookie."
    },
    {
      "id": "cross-origin-embedder-policy",
      "name": "Cross-Origin-Embedder-Policy",
      "result": "coep-not-implemented",
      "summary": "not sent",
      "pass": true,
      "modifier": 0,
      "evidence": null,
      "fix": "Send `Cross-Origin-Embedder-Policy: require-corp` (or credentialless) once embedded content allo…"
    },
    // … 9 more items
  ],
  "hstsPreload": {
    "status": "preloaded",
    "preloadedBy": "wikipedia.org"
  },
  "software": [
    {
      "id": "ats",
      "name": "Apache Traffic Server",
      "version": "9.2.15",
      "header": "server"
    }
  ],
  "cookies": [
    {
      "name": "WMF-Last-Access",
      "host": "www.wikipedia.org",
      "secure": true,
      "httpOnly": true,
      "sameSite": "absent",
      "partitioned": false,
      "path": "/",
      "domain": null,
      "maxAge": 2735169,
      "valueLength": 11,
      "problems": []
    },
    {
      "name": "WMF-Last-Access-Global",
      "host": "www.wikipedia.org",
      "secure": true,
      "httpOnly": true,
      "sameSite": "absent",
      "partitioned": false,
      "path": "/",
      "domain": "wikipedia.org",
      "maxAge": 2735169,
      "valueLength": 11,
      "problems": []
    },
    {
      "name": "GeoIP",
      "host": "www.wikipedia.org",
      "secure": true,
      "httpOnly": false,
      "sameSite": "absent",
      "partitioned": false,
      "path": "/",
      "domain": "wikipedia.org",
      "maxAge": null,
      "valueLength": 30,
      "problems": []
    },
    // … 2 more items
  ],
  "issues": [
    {
      "severity": "error",
      "code": "csp_not_implemented",
      "message": "Content-Security-Policy: not sent (-25). Send a Content-Security-Policy. Start it as Report-Only…"
    },
    {
      "severity": "error",
      "code": "x_frame_options_not_implemented",
      "message": "X-Frame-Options: not sent (-20). Send `X-Frame-Options: DENY`, or CSP `frame-ancestors 'self'`."
    },
    {
      "severity": "warning",
      "code": "x_content_type_options_not_implemented",
      "message": "X-Content-Type-Options: not sent (-5). Send `X-Content-Type-Options: nosniff`."
    },
    // … 2 more items
  ],
  "tookMs": 1059
}

Shortened for the page: long lists show their first items.