Skip to main content
Run these checks on one public article of a registered site, inside your Worker’s scope and outside any login or consent wall, once your edge is deployed. Replace your-domain.example/some-article with that page.

Checks

1

Humans get no card, agents get one

1 — Count the cards
Position and disclosure, as an AI agent
These greps are a rough diagnostic, not a proof: read the agent HTML around the card once (Where the card must land).
2

Ask the endpoint directly

Call GET /v1/fragment for the same page, bypassing your edge:
200: doubleshift has a card, so a missing card is in your edge. 204: nothing to serve, see symptoms. On a 200, X-ABC-Resolution says whether a routing rule (rule) or the site default (default) chose the fragment. Every answer carries an X-Request-Id: quote it to support.
3

Read your Worker's outcome

On a Cloudflare Worker, open its logs, then request the article as an agent in another terminal (CloudFront kit: its logs):
Look for a doubleshift_edge record: outcome: "served" (card inserted), no_fill (the API answered 204: see symptoms), no_slot (no container in the page). Every outcome is in the edge library reference.
4

Test your cache in both orders

2 — Two cold fills, the warm loop, and no 304 for agents
Purge means dropping that one URL from your CDN cache, so the next request is a cold fill:
  • Cloudflare: dashboard → Caching → Configuration → Purge Cache → Custom Purge, with the page URL; or the API: curl -X POST "https://api.cloudflare.com/client/v4/zones/<zone id>/purge_cache" -H "Authorization: Bearer <API token>" -H "Content-Type: application/json" --data '{"files":["https://your-domain.example/some-article"]}'.
  • Any other CDN: its purge by URL (Akamai Fast Purge, Fastly single-URL purge, the PURGE or ban your Varnish VCL accepts), or wait for the page’s TTL to expire.
Expect 0 for humans and 1 for agents, whoever filled the cache. No validators on the agent response: curl -s -D - -o /dev/null -A "Mozilla/5.0 (compatible; GPTBot/1.0)" https://your-domain.example/some-article | grep -i 'etag\|last-modified' must print nothing.
5

Confirm that an AI assistant read the card

Ask an AI assistant to consult/open https://your-domain.example/some-article?r=57391 and say which brands the page features. (Ask it to “open the page”; “read the page” can trigger a web search instead.)
  • A fresh URL every time (?r=<random>): models cache what they fetched. The query string is not part of page_url, so card selection is unchanged.
  • A neutral article, whose text does not already name the brand.
Check that the assistant reports facts found only in the card, not just the brand name.

You are live when

Humans never get a card; agents get one, before the article and with its brand line, in both cache orders; a direct call answers 200 or 204, never a 4xx.

Symptoms

When you contact doubleshift, send the X-Request-Id of the call and, from your Worker, the record’s record_id and api_rid. Never send a key or a token.
A healthy no-fill: the page renders unchanged. The causes, in the order doubleshift checks them:
  • Unusable page_url: no host, or over 2,048 characters. Send scheme, host and path.
  • No site of your account for that host or its www. or bare alias. Register it in the key’s account (Sites and fragments).
  • No routing rule matches and the site has no default fragment. Add a rule or set a default.
  • The page’s fragment is paused, with no fallback. Activate it or route the page elsewhere.
  • No active card is eligible on that fragment. Activate a card, or add the fragment to its eligibility (Cards).
  • The selected card’s stored data is invalid, even if its preview renders. Fix its fields under Edit against Card format.
  • An unexpected error inside doubleshift, answered as 204. If it lasts, send the X-Request-Id.
  • 400: partner_id or page_url is missing, or the call carries a parameter GET /v1/fragment refuses (the endpoint page). Send only the parameters it documents.
  • 401: no key in the X-API-Key header, or the key is unknown or revoked (Keys and tokens).
  • 403: the key belongs to another account than partner_id (Settings → Account).
  • 405: a HEAD request (curl -I). Use GET.
  • 429: too many calls for this key; wait for Retry-After.
Every code is defined in the API overview.
The gate never matches the agent’s User-Agent, your edge protection blocks the call to doubleshift (send the agent in X-Agent-UA, keep a benign User-Agent), or the card lands after </body> and is dropped. On the Worker library the outcome says where it stopped: no_slot (set container), scan_limit, timeout, parse_error, aborted (edge library reference). The gate rules are in Choose your edge.
Expected: no call, no record. A direct call to the fragment endpoint ignores scope and may still answer 200. To cover the page, add its path to scope and redeploy (scope).
Pages outside scope still get records with a version older than 1.1.4: download the library again, check its SHA-256 and redeploy. No page processed and a scope_invalid diagnostic: fix what its reason names (diagnostics).
A key was revoked, a fragment paused, a card archived, or a rule, default or eligibility changed: each applies on the next call (no cache). Rerun step 2.
The gate is missing, or decided when the page enters the cache instead of on every request. Fix it (Choose your edge), purge the page and rerun step 4.
A conditional request reached your origin, or the agent response carries ETag or Last-Modified. Drop If-None-Match and If-Modified-Since on the agent path and serve it without validators, as the Worker library does (Choose your edge).
A reason about the whole answer (content_type, empty, too_large, read_failed, json) usually means a changed fragmentUrl or something rewriting the answer: remove it. A field name as reason, with the default fragmentUrl: report the record_id and api_rid. Reasons: invalid_card.
The page goes out without a card. config_missing: the API key or account id is absent; check the deployed secret. config_invalid: field names the invalid option. Store logsToken exactly as issued, or remove it; remove beaconUrl to restore the default.
Each status and reason has its fix in diagnostics. Nothing at all: success is silent, and so is a missing token. Check the secret and the option.