# Acquire Licenses for Protected Content Source: https://connect-docs.supertab.co/guides/acquire-license Obtain a license token as a crawler operator and use it to request protected content. This guide covers how to set up your bots, agents, crawlers, and other automated systems to get licensed access to RSL-protected content. Use the SDK to obtain a license token for the content you want to access, then send that token in the request to the publisher. ## Before You Start You need a **Supertab Connect customer account** and a registered **System** representing your bot or agent. 1. **Create your account** — sign up at [customer-connect.supertab.co](https://customer-connect.supertab.co/signup) 2. **Register a System** — in the dashboard, go to **Systems → Create System** and give it a descriptive name (e.g., "Production RAG Agent", "News Indexer") 3. **Generate credentials** — on the System Details page, generate a `client_id` and `client_secret` 4. **Save your credentials** — you will need both the `client_id` and `client_secret` to obtain license tokens You also need: * The exact protected resource URL you want to access, for example `https://publisher.com/premium/article-123` * An active license agreement between your organization and the publisher ## Acquire Licenses 1. Your system identifies the exact URL it wants to access 2. The SDK fetches and evaluates the publisher's `license.xml` 3. It finds the best matching content rule for the requested resource. 4. The SDK then requests a license token for the appropriate content pattern from the License Server using your credentials. 5. Send your license token as an `Authorization: License ` heade on subsequent requests. Call `obtainLicenseToken()` for **every page you wish to access**. The SDK handles determining the licensing basis for you and manages token expiration. ```ts theme={null} import { SupertabConnect } from "@getsupertab/supertab-connect-sdk"; const CLIENT_ID = "...your customer system client_id..."; const CLIENT_SECRET = "...your customer system client_secret..."; const resourceUrl = "https://publisher.com/premium/article-123"; const accessToken = await SupertabConnect.obtainLicenseToken({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, resourceUrl, }); ``` Send the token in the `Authorization` header using the `License` scheme. ```ts theme={null} const response = await fetch("https://publisher.com/premium/article-123", { headers: { Authorization: `License ${accessToken}`, }, }); ``` Use `License`, not `Bearer`. The header scheme must match the expected RSL license token format. ## Full Example ```ts theme={null} import { SupertabConnect } from "@getsupertab/supertab-connect-sdk"; const resourceUrl = "https://publisher.com/premium/article-123"; const accessToken = await SupertabConnect.obtainLicenseToken({ clientId: process.env.SUPERTAB_CLIENT_ID!, clientSecret: process.env.SUPERTAB_CLIENT_SECRET!, resourceUrl, }); const response = await fetch(resourceUrl, { headers: { Authorization: `License ${accessToken}`, }, }); if (!response.ok) { throw new Error(`Request failed with status ${response.status}`); } const content = await response.text(); ``` ## Related Docs TypeScript-specific reference with CDN handler examples. Token acquisition, client authentication, and JWKS verification. # Deploy at the Edge Source: https://connect-docs.supertab.co/guides/deploy-cdn Serve your RSL license and enforce CAP at the CDN edge using the Supertab Connect SDK. This guide walks you through the two things every CDN deployment needs: serving your RSL license at your domain, and enforcing the Crawler Authentication Protocol (CAP) to protect your content from unlicensed crawlers. This is a general guide. Specific guidance for each CDN is available: VCL and Compute, including service chaining. CloudFront Functions, Lambda\@Edge, and Terraform. Workers for RSL and CAP enforcement. Generic patterns for any platform. ## Before You Start You need: * A **Supertab Connect merchant account** – [contact sales to sign up](https://www.supertab.co/contact) * A **Website** registered in the Supertab Connect dashboard with your domain's base URL * Your **Website URN** — found in your Website settings, e.g `urn:stc:merchant:system:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` * A **Merchant API key** — generated under Website Details → API Keys in the dashboard * Access to your **CDN configuration** for the domain you want to protect *** ## Part 1: Serve Your RSL License Your RSL license needs to be accessible at `https://yourdomain.com/license.xml`. Supertab Connect hosts the license content — your CDN proxies the request to our origin and rewrites the URL so it stays on your domain. **1. Add Supertab Connect as an Origin** Add `api-connect.supertab.co` as an origin (sometimes called a backend, upstream, or host) with HTTPS on port 443. **2. Add routing for `/license.xml`** Create a rule, behavior, or condition that matches requests to `/license.xml` exactly and directs them to the Supertab Connect origin. **3. Add a URL rewrite** Before the request reaches the origin, rewrite the path to include your Website URN: ``` /license.xml → /merchants/systems/YOUR_WEBSITE_URN/license.xml ``` e.g in a JS based runtime ```javascript theme={null} if (request.path === "/license.xml") { request.path = "/merchants/systems/YOUR_WEBSITE_URN/license.xml"; } ``` Your CDN must send `Host: api-connect.supertab.co` to the origin — not your own domain. If you see 502 errors, check the host header override setting. **Verify it works:** visit `https://yourdomain.com/license.xml` in your browser and confirm you see your RSL license XML. *** ## Part 2: Run the Supertab Connect SDK The SDK validates the `Authorization: License ` header on crawler requests. You deploy it as an edge worker or function that runs before your origin. ### Install the SDK ```bash theme={null} npm install @getsupertab/supertab-connect-sdk ``` ### Wire the request handler Instantiate `SupertabConnect` once (it's a singleton) and call `handleRequest` on each incoming request. The SDK handles bot detection, token extraction, JWT verification, enforcement, and analytics (when enabled). You act on the result: ```javascript highlight={4, 16} theme={null} import { SupertabConnect, EnforcementMode, HandlerAction } from "@getsupertab/supertab-connect-sdk"; const connect = new SupertabConnect({ apiKey: YOUR_API_KEY, // best practice is to retrieve the key from your CDNs secret store. enforcement: EnforcementMode.OBSERVE, analyticsEnabled: true, // emit events for bot classification botDetector: (request) => { // optional — extend or replace the built-in UA heuristics const ua = request.headers.get("User-Agent") || ""; return ua.includes("MyBot"); }, }); // In your request handler: const result = await connect.handleRequest(request); if (result.action === HandlerAction.BLOCK) { return new Response(result.body, { status: result.status, headers: result.headers, }); } // ALLOW — forward to origin return fetch(request); ``` ### Configure the essentials **API key** — Your Merchant API key from the Supertab Connect dashboard, stored as a secret in your CDN's secret management system. Never hardcode it. **Bot detection** — The SDK includes built-in user-agent heuristics to identify crawler traffic. You can extend or override them by passing a `botDetector` function with your own signals: ```javascript Custom Bot Detection focus={1-4, 9} theme={null} const isBot = (request) => { const ua = request.headers.get("User-Agent") || ""; return ua.includes("MyCustomBot") || ua.includes("Scraper"); }; const connect = new SupertabConnect({ apiKey: YOUR_API_KEY, enforcement: EnforcementMode.OBSERVE, botDetector: isBot }); ``` ```javascript Common UA Detection focus={1, 6} theme={null} import { defaultBotDetector } from "@getsupertab/supertab-connect-sdk"; const connect = new SupertabConnect({ apiKey: YOUR_API_KEY, enforcement: EnforcementMode.OBSERVE, botDetector: defaultBotDetector }); ``` **Analytics** — Off by default. Set `analyticsEnabled: true` to emit one event per request; this is what powers bot classification and traffic reporting in your dashboard. It's a separate toggle from enforcement. **Enforcement mode** — Start with `OBSERVE` (the default), which verifies tokens and records outcomes but never blocks. This lets you observe which requests would be blocked before enabling hard enforcement. Switch to `ENFORCE` once you are confident in your bot detection: ```javascript theme={null} import { EnforcementMode } from "@getsupertab/supertab-connect-sdk"; // DISABLED: skip verification entirely — all requests pass through // OBSERVE (default): verify tokens and record outcomes, but never block // ENFORCE: block requests with a missing or invalid license token (401) ``` Deploy in OBSERVE mode first. If your bot detection is too broad, ENFORCE could block legitimate human visitors. Review your traffic patterns before switching. *** ## Part 3: Update robots.txt Add a `License:` directive to your `robots.txt` so crawlers can discover your license: ```txt theme={null} License: https://yourdomain.com/license.xml ``` The URL must be fully qualified. Place it at the top of the file, before any `User-agent:` directives. **Example:** ```txt theme={null} License: https://yourdomain.com/license.xml User-agent: * Allow: / Sitemap: https://yourdomain.com/sitemap.xml ``` *** ## Cache Invalidation Your CDN will cache the license response. If you update your license and need the change reflected immediately, purge `https://yourdomain.com/license.xml` from your CDN's cache. See your CDN's reference page for the exact purge command. *** ## CDN-Specific Guides Each reference page covers the full setup for that platform — origins, behaviors, functions, Terraform configs, and SDK integration patterns. VCL and Compute service options, including VCL-to-Compute service chaining. CloudFront Functions for RSL, Lambda\@Edge for CAP, with a Terraform alternative. Worker-based RSL proxy and SDK Worker for CAP enforcement. Generic patterns for any CDN not listed above. # Deploy on WordPress Source: https://connect-docs.supertab.co/guides/deploy-wordpress Serve your RSL license and enforce CAP on a WordPress site using the Supertab Connect plugin. The Supertab Connect WordPress plugin handles both RSL license serving and CAP enforcement without CDN configuration or custom code. It intercepts requests to `/license.xml`, fetches and caches your license from Supertab Connect, and validates `Authorization: License` tokens on automated requests. * **Requirements:** WordPress 6.4 or higher (self-hosted or WordPress VIP). * **Plugin:** [Supertab Connect](https://wordpress.org/plugins/supertab-connect/) ## Before You Start You need: * A **Supertab Connect merchant account** – [contact sales to sign up](https://www.supertab.co/contact) * A **Website** registered in the dashboard — select **WordPress** as the integration type * Your **Website URN** — found in Website settings, looks like `urn:stc:merchant:system:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` *** ## Install the Plugin In your WordPress admin, go to **Plugins → Add Plugin**, search for **Supertab Connect**, install, and activate. The plugin will redirect you to its settings page on first activation. *** ## Configure RSL In **Settings → Supertab Connect**, paste your **Website URN** into the Website URN field and save. Once saved, visit `https://yourdomain.com/license.xml` to confirm your RSL license is being served. The license is cached in the WordPress database. If `/license.xml` doesn't reflect recent changes, click **Purge license.xml from cache** in the **Your RSL License** section, then reload. If it's still stale, your hosting stack may have its own caching layer — purge `/license.xml` from your host's control panel too. *** ## Configure CAP In the Supertab Connect dashboard: 1. Open your **Website Details** 2. Go to the **API Keys** section 3. Click **Generate new key** 4. Copy the generated key Treat the Merchant API key as a secret. Do not expose it in frontend code or share it outside the WordPress admin environment. In WordPress, go to **Settings → Supertab Connect**: 1. Find the **License Verification** section 2. Paste the generated Merchant API key into the **Merchant API key** field 3. Click the **Enable CAP** checkbox 4. Click **Save Changes** By default, CAP protects your entire site using a single `*` wildcard path. You can narrow protection to specific sections of your site. 1. In the **Active Paths** section, review the default path 2. Optionally remove `*` and add specific path patterns 3. Click **Add Path** to add additional patterns 4. Click **Save Changes** **Path pattern examples:** | Pattern | Matches | | ----------- | -------------------------- | | `*` | Entire site (default) | | `blog/*` | All URLs under `/blog/` | | `premium/*` | All URLs under `/premium/` | | `pricing` | Only the `/pricing` page | Paths support `*` for wildcards. Both `/sample-page` and `/sample-page/` (trailing slash) are treated as the same path. After saving the plugin settings: 1. Return to the Supertab Connect dashboard 2. Use **Verify Setup** for CAP 3. Confirm that verification succeeds Once verification passes, CAP is active on your WordPress site. *** ## Analytics & Bot Classification Available in the Supertab Connect plugin **1.3.0-beta** and later. Once your Merchant API key is saved (see [Configure CAP](#configure-cap)), you can share agent & bot traffic analytics with Supertab to get classification and traffic insights in your dashboard. In **Settings → Supertab Connect**, check **Enable agent & bot classification**, then click **Save Changes**. It's **off by default** and is a separate toggle from **Enable CAP** — you can turn on analytics whether or not you enforce CAP. *** ## Update robots.txt Add a `License:` directive to your `robots.txt` so crawlers can discover your license: ```txt theme={null} License: https://yourdomain.com/license.xml ``` The URL must be fully qualified. Place it at the top of the file, before any `User-agent:` directives. *** ## Publishing New Versions When you publish a new license version in Supertab Connect, the plugin picks it up automatically — `license.xml` updates on the next fetch. Because the license is cached in the WordPress database, changes may not appear right away. To refresh immediately, go to **Settings → Supertab Connect → Your RSL License** and click **Purge license.xml from cache**. Then confirm the update at `https://yourdomain.com/license.xml`. If the update still doesn't appear, your hosting environment may have an additional caching layer in front of WordPress. Purge `/license.xml` from your host's control panel too. *** ## Troubleshooting | Problem | What to check | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `license.xml` returns 404 | Confirm the Website URN is saved correctly in plugin settings | | CAP is not enforcing | Confirm the CAP checkbox is enabled, the API key matches the current key in the dashboard, and the plugin settings were saved | | License shows stale content | Click **Purge license.xml from cache** in the plugin's **Your RSL License** section; if still stale, purge your hosting cache for `/license.xml` | | Human visitors are being blocked | Check that your WordPress permalink settings are not interfering with the plugin's request interception | | CAP verification fails in dashboard | Confirm the Merchant API key in WordPress matches the current key in Supertab Connect | *** ## Next Steps Test the protected flow by obtaining a license token and making a licensed request. How CAP works, what it enforces, and what it leaves to you. # Supertab Connect Source: https://connect-docs.supertab.co/introduction/overview License and control scraper access to your content using the RSL open standard. Supertab Connect lets publishers define and enforce licensing terms for automated access to their content — crawlers, AI agents, and other bot traffic. Crawler operators use Supertab Connect to discover licensing terms and gain licensed access to content. Licensing Components: * **RSL License**: Declares what automated clients are permitted to do with or without a license. * **Open Licensing Protocol (OLP)**: Grants licenses to authorized clients. * **Crawler Authentication Protocol (CAP)**: Runs at the edge to validate licenses before crawlers reach your content. *** ## How It Works Publish a `license.xml` on your domain expressing licensing terms in the [RSL standard](https://rslstandard.org/) format. Crawlers that want access obtain a license token from Supertab Connect and present it on every request. The Supertab Connect SDK – running in your CDN or application – validates the token in real time. ```mermaid theme={null} sequenceDiagram participant Bot as Crawler participant API as Supertab Connect API participant Edge as CDN Edge participant Origin as Publisher Origin Bot->>API: Request license token API->>Bot: License token (JWT) Bot->>Edge: GET /content (Authorization: License ) alt Valid token Edge->>Edge: Verify signature and claims Edge->>Origin: Forward request Origin->>Edge: Content Edge->>Bot: 200 OK + Content Edge-->>API: Record usage event (async) else Invalid or missing token Edge->>Bot: 401 + WWW-Authenticate + Link headers end ``` *** ## Built on RSL [RSL (Really Simple Licensing)](https://rslstandard.org/) is an open standard for machine-readable content licensing. A publisher serves a `license.xml` file at their domain. Crawlers fetch and parse it, discover what access is available, and obtain license tokens to access content under the agreed terms. An RSL license answers four questions for any content scope: which URLs the rule applies to, which automated uses are permitted (`search`, `ai-input`, `ai-index`, `ai-train`), which uses are prohibited, and what commercial or legal conditions apply. ```xml theme={null} search ai-train ``` This says: classic search indexing is allowed for `/articles/*`, but training AI models on the content is not. Crawlers discover your license via a `License:` directive in `robots.txt`. From there, a compliant crawler fetches the license, evaluates the terms, and can request a token if licensed access is available. [Content Licensing with RSL →](/licensing/licensing-overview) covers the full license structure in detail — scope rules, vocabulary, legal elements, conflict resolution, and common patterns. *** ## Enforcement at the Edge The [Crawler Authentication Protocol (CAP)](/licensing/crawler-authentication-protocol) runs at your CDN edge to verify that crawlers hold a valid license before allowing access to your content. The Supertab Connect SDK handles the full lifecycle on every request identified as bot traffic: 1. **Detection** — identify automated traffic via bot detection heuristics or custom logic 2. **Verification** — validate the license token signature and claims 3. **Enforcement** — allow or block based on token validity 4. **Recording** — log the event for analytics and billing Human browser traffic passes through without any of these steps. CAP operates entirely at the network layer — no footprint on your origin, no impact on human visitors. Tokens themselves are acquired through the [Open Licensing Protocol (OLP)](/licensing/open-licensing-protocol), which handles client authentication and token issuance via Supertab Connect. Supertab Connect is also available as a [Wordpress Plugin](../guides/deploy-wordpress). *** ## Who Is Supertab Connect For? **Publishers** are websites and API providers that want to license and monetize automated access to their content. You define licensing terms in the RSL Editor, publish `license.xml` at your domain, and deploy CAP enforcement at your CDN edge. **Crawler Operators** are organizations running bots, scrapers, or AI agents that need licensed access to content. You register your systems with Supertab Connect, obtain license tokens via the SDK, and present them when requesting protected content. *** ## Where to Start **If you are a publisher** looking to license and protect your content, pick the deployment guide that matches your infrastructure: Serve your RSL license and enforce CAP at the edge — Fastly, CloudFront, Cloudflare, or any CDN. Install the Supertab Connect plugin to handle RSL and CAP without CDN configuration. **If you are a crawler operator** looking to access licensed content with your bots or AI agents: Register your system, obtain license tokens, and make authenticated requests. # Crawler Authentication Protocol (CAP) Source: https://connect-docs.supertab.co/licensing/crawler-authentication-protocol How CAP works, what it enforces, what it leaves to you, and how it fits into your existing infrastructure. The Crawler Authentication Protocol (CAP) is the enforcement layer of Supertab Connect. It gives your CDN edge the ability to verify that a crawler holds a valid license before allowing it to reach your content. Compliant crawlers present a license token with every request; the edge validates it in real time and either forwards the request or returns a structured rejection. CAP operates entirely at the network layer. It has no footprint on your origin, no impact on human browser traffic, and no dependency on your application code. ## How CAP works The protocol follows a straightforward request-response cycle. A crawler that holds a valid license attaches it to every request using the `Authorization` header: ```http theme={null} Authorization: License ``` Your CDN edge — running the Supertab Connect SDK — intercepts the request. If the request looks like bot traffic, the SDK extracts the token and verifies its JWT signature against Supertab Connect's JWKS endpoint. A valid token allows the request through to your origin. An invalid or missing token receives a rejection with structured headers pointing the crawler to where it can obtain a license: ```http theme={null} HTTP/1.1 401 Unauthorized WWW-Authenticate: License error="invalid_request" Link: ; rel="license"; type="application/rsl+xml" ``` The status code depends on the failure: `401` for missing, expired, or invalid tokens, `403` for tokens that are valid but don't cover the requested resource. The `WWW-Authenticate` header tells the crawler what went wrong. The `Link` header tells it where your RSL license lives, which is where it can discover how to acquire access. Non-bot traffic — browsers, APIs, anything that doesn't present itself as a crawler — passes through unchanged. CAP only enforces against traffic your bot detection logic identifies as automated. ## What the token contains Tokens are signed JWTs issued by Supertab Connect as part of the license acquisition flow. They carry the identity of the licensed crawler, the merchant system it is licensed against, and an expiry. The SDK validates the JWT signature against Supertab Connect's JWKS (JSON Web Key Set) endpoint and checks the claims on every request — no session state or per-request call to the license server is required at your edge. Tokens are short-lived by design. A crawler must obtain and refresh tokens as part of normal operation, which means the enforcement signal is current: a valid token reflects an active license, not a historical one. ## What CAP does not do CAP is specifically scoped. Understanding its limits helps you integrate it cleanly rather than expecting it to cover more than it does. **CAP does not identify bots.** The SDK validates tokens for traffic your bot detection logic flags as automated. You are responsible for defining what counts as a bot in your infrastructure. The SDK accepts a `botDetector` function for this purpose; without one it applies its own built-in heuristics based on user-agent patterns, but custom logic is almost always more accurate. **CAP does not enforce on human traffic.** Browsers do not present `Authorization: License` headers. Any request without that header that your bot detection logic also passes through is treated as human traffic and forwarded without token validation. **CAP does not issue tokens.** Token acquisition is handled by the Open Licensing Protocol (OLP). CAP only validates tokens that already exist. A crawler that has never gone through the licensing flow will receive a `401` and the information it needs to start that process. **CAP does not rate-limit or throttle.** It validates credentials. Crawl rate control is a separate concern handled at your infrastructure level. ## How CAP fits with your bot detection The two most important operational decisions when deploying CAP are where to run it and how to identify bots. CAP runs at the CDN edge — before your origin sees the request. This matters for two reasons: it keeps validation latency low, and it means rejected requests never reach your servers at all. For high-volume crawlers, this has meaningful infrastructure implications. Bot detection is the input to CAP enforcement. If your bot detection is too narrow, licensed crawlers will slip through as apparent human traffic without token validation. If it is too broad, legitimate human users may be subjected to token checks they cannot pass. The SDK's built-in detection covers known crawler user-agent patterns, but any serious deployment should be reviewed with your own traffic patterns in mind. ## Enforcement modes The SDK supports three enforcement modes: **OBSERVE** is the default. Requests pass through, but the SDK verifies tokens, records outcomes, and attaches licensing headers to unlicensed bot requests. It is useful for initial rollout — you can observe which requests would be blocked before enabling hard enforcement. (A bot presenting an invalid token is still blocked.) **ENFORCE** rejects requests from identified bots without a valid license token with a `401` (or `403` for a valid token that doesn't cover the resource). Switch to this once you have validated that your bot detection logic is not catching human traffic. **DISABLED** turns off verification entirely. Requests are allowed without licensing intervention. This is useful during initial SDK integration when you want to confirm the deployment works without any enforcement side effects. ## Related Docs Deploy CAP enforcement at your CDN edge, with links to platform-specific guides. How your RSL license expresses what licensed crawlers are permitted to do. # Content Licensing With Really Simple Licensing Source: https://connect-docs.supertab.co/licensing/licensing-overview Your `license.xml` is how you tell crawlers, AI agents, and other automated clients what they can and cannot do with your content. It is served at your domain and expressed in the [RSL standard](https://rslstandard.org/) format — a machine-readable structure that covers scope, permissions, restrictions, and commercial terms. At a practical level, an RSL file answers four questions: 1. Which content does this rule apply to? 2. Which automated uses are allowed? 3. Which uses, users, or geographies are restricted? 4. What legal or commercial conditions sit behind those rights? That is why `license.xml` matters. It is not just documentation. It is the structured expression of your licensing position for search engines, AI systems, crawlers, and other automated clients. The example below shows the same content section expressed through two separate `` declarations. The document root uses the RSL namespace, and RSL documents are served as `application/rsl+xml`. ```xml theme={null} https://publisher.example.com/schema/news.jsonld https://publisher.example.com/licensing-terms Example Publishing Group search commercial non-commercial education government personal ownership authority as-is no-liability ai-input ai-index commercial ai-train true mailto:licensing@publisher.example.com ``` Read it this way: * `/news/*` is the content scope * one content declaration allows classic search broadly * another adds commercial AI input and AI indexing through a license-server flow * AI training remains prohibited This split matters. Under the RSL specification, if a content declaration includes a license server, clients are expected to obtain licensed access for that content even when the applicable payment condition is free. That is why a broad search-allowed case should not carry a `server` value if you want unlicensed search access to remain possible. Supertab Connect takes care of this pattern in the RSL Editor engine, so users don't have to worry about accidentally losing on SEO optimization and can simply focus on creating their terms for licensed access. ## How clients evaluate scope and conflicts Clients do not evaluate an RSL file by reading it top to bottom, but by scope and specificity. The practical rules that matter are: * More specific content scopes take precedence over broader scopes * Prohibitions override permissions when both apply * Overlapping, unclear licenses are interpreted conservatively That has direct commercial consequences. If you publish a broad rule for `/` and a narrower rule for `/premium/*`, the narrower rule should govern the premium section. If you publish overlapping license offers that apply to the same content and same audience without a clear distinction, a well-behaved client is likely to default to the stricter interpretation. If two overlapping licenses both appear to apply and one permits a use while the other prohibits it, complying clients would apply the prohibition. Ambiguous drafting does not expand rights. It usually reduces licensed access. ## Core license elements ### ``: the scope of the policy `` defines which asset, path, section, file, or resource the rules apply to. For most websites, this is the key commercial control point because it lets you distinguish public, premium, archive, research, or other sections. | Part | Meaning | | ------------- | ---------------------------------------------------------------------------- | | `url` | Canonical scope identifier, expressed as a path pattern | | `server` | License server clients use the server to obtain and validate licensed access | | `lastmod` | Freshness metadata for the scoped asset or rule set | | `` | Structured metadata associated with the content | | `` | Alternative machine-friendly version of the same content | | `` | Rights holder identity and contact | | `` | Human-readable legal or commercial terms | For normal web licensing, path patterns are usually the simplest model: * `/` * `/news/*` * `/articles/$` * `/premium/*` The main discipline is simple: use broad scopes only when the same policy genuinely applies across that whole area, and use narrower scopes when the rules differ in a meaningful way. Short practical notes: * `server` matters when licensed access requires token acquisition. It connects the public license to the operational license acquisition flow. * if you want to license content without requiring token acquisition, keep that content declaration separate from token-gated licensed uses and do not include a `server` value in the allowing content declaration. * `lastmod` helps clients re-check freshness, but it does not change the rights themselves. ### ``: one coherent rule bundle A `` element can contain one or more `` elements. Each one represents a coherent set of rights and conditions for that scope. In practice, multiple licenses make sense when the offers are genuinely different, for example: * search allowed for everyone, but AI use reserved for commercial licenses * non-commercial use under one offer, commercial use under another * one geography under one set of terms, another geography under another What does not work well is publishing multiple licenses that apply to the same scope and same audience with only minor wording differences. That creates ambiguity without adding commercial flexibility. ### ``: what is allowed `` is the positive grant of rights. It declares which uses, user classes, or geographies are allowed. The three main permit types are: * `usage` * `user` * `geo` #### `type="usage"` This is the most commercially important vocabulary because it defines what automated systems may actually do with the content. | Token | Meaning | | ---------- | ----------------------------------------------------------------------------- | | `search` | Traditional search indexing and search results, not AI-generated summaries | | `ai-input` | Use as input to generate AI answers or summaries | | `ai-index` | Storage in an AI retrieval or indexing layer | | `ai-train` | Training or fine-tuning models on the content | | `ai-all` | Any AI-system use across input, indexing, training, and related AI operations | | `all` | Any automated processing, including AI and non-AI use | The most important distinction is that `search` does not mean AI summaries. If your goal is “allow classic search, but do not allow AI grounding or training,” `search` is the right starting point. #### `type="user"` This lets you separate rights by operator class rather than by technical use. | Token | Meaning | | ---------------- | ------------------------------- | | `commercial` | For-profit or commercial use | | `non-commercial` | Non-commercial use | | `education` | Educational use | | `government` | Public-sector or government use | | `personal` | Individual personal use | #### `type="geo"` This lets you scope rights geographically, usually with ISO 3166-1 alpha-2 country or region codes. Example: ```xml theme={null} US EU ``` ### ``: what is forbidden `` is the hard stop. When something appears in both `permits` and `prohibits`, the prohibition wins. That makes it useful for carve-outs. ```xml theme={null} ai-all ai-train ``` This says: AI use is allowed in general, but training is excluded. That is much clearer than trying to imply the same thing through silence or overlapping offers. ### ``: legal posture and proof points The legal block expresses what you affirm, what you disclaim, and where counterparties can go for rights clarification. | `type` | Purpose | | ------------- | ------------------------------------------------------------------------ | | `warranty` | Positive statements about rights, authority, or asset quality | | `disclaimer` | Liability and warranty disclaimers | | `attestation` | Boolean affirmation that you are authorized to make the rights statement | | `contact` | Legal or rights contact point | | `proof` | URI to supporting evidence of authority or rights | The values most management teams usually care about are: * warranties such as `ownership`, `authority`, and `no-infringement` * disclaimers such as `as-is` and `no-liability` * whether `attestation` is appropriate for your internal governance standard Practical guidance: * publish warranties only when you are comfortable making them consistently and publicly * keep the legal contact monitored by someone who can actually answer rights questions * use proof links only when they point to meaningful evidence, not filler ### ``: commercial terms RSL can also express payment-related terms. At a protocol level, licenses may include payment conditions, references to standard or custom commercial terms, and pricing metadata. For this overview, the important point is strategic rather than structural: RSL is designed not only to say “yes” or “no,” but also to support licensed access under commercial conditions. ## Supporting metadata The following elements do not usually change the core permission logic, but they make the license more credible, easier to interpret, and easier to operate. | Element | What it adds | | ------------- | ----------------------------------------------------------- | | `` | Structured metadata associated with the content | | `` | Alternate machine-friendly representation of the same asset | | `` | Rights holder identity plus contact information | | `` | Human-readable legal or commercial terms page | For most publishers, this is straightforward: publish real ownership metadata, a real rights contact, and a real terms page. Placeholder metadata weakens confidence in the license even if the XML is technically valid. ## Common policy patterns These are two of the most common patterns publishers use when deciding how open or restrictive to be. ### Allow classic search, but not AI use ```xml theme={null} search ai-all ``` This is clear and commercially easy to explain: search discovery is acceptable, but AI reuse is not. ### Allow AI answers or retrieval, but not training ```xml theme={null} ai-input ai-index ai-train ``` This is useful when you are open to answer-generation or retrieval use, but you do not want the content absorbed into model training. ## Discovery Publishing `license.xml` is not enough on its own. Clients also need a reliable way to discover it. ### Primary recommendation: `robots.txt` For websites, this is the simplest and most important discovery path. Add this line to your `robots.txt`: ```txt theme={null} License: https://yourdomain.com/license.xml ``` The URL must be fully qualified. If you only implement one discovery mechanism, this should usually be it. ### Secondary discovery options | Method | Best used for | | --------------------------- | ------------------------------------------------------ | | HTML `` | HTML pages where page-level association matters | | HTTP `Link` header | APIs, JSON responses, media files, and non-HTML assets | Examples: ```html theme={null} ``` ```http theme={null} Link: ; rel="license"; type="application/rsl+xml" ``` ## Best practices * Keep the number of scopes small and commercially meaningful. A few clear boundaries are better than many overlapping ones. * Separate search, AI input, AI indexing, and AI training deliberately. They are different rights with different implications. * Avoid ambiguous overlapping license offers. If a counterparty cannot tell which license applies, you have weakened the commercial outcome. * Make discovery and contact simple: publish at `/license.xml`, advertise it in `robots.txt`, and use a real rights contact. ## Related Docs Publish your `license.xml` at your own domain and enforce licensing at the edge. # Open Licensing Protocol (OLP) Source: https://connect-docs.supertab.co/licensing/open-licensing-protocol How OLP works, what it provides, how Supertab Connect implements it, and how it connects to CAP enforcement. The Open Licensing Protocol (OLP) is the token acquisition layer of the RSL standard. It defines how a crawler authenticates with a license server, requests a license token for a specific piece of content, and receives a credential it can present when accessing that content. OLP extends OAuth 2.0 with licensing-specific semantics — the grant type is `client_credentials`, and the resulting token type is `License` rather than `Bearer`. Where [CAP](/licensing/crawler-authentication-protocol) answers "does this crawler have a valid license?", OLP answers the earlier question: "how does a crawler get a license in the first place?" ## How OLP works The protocol involves four parties: * **Publisher** — defines licensing terms in an RSL document * **Client** — a crawler, bot, or AI agent that wants licensed access * **License Server** — authenticates clients and issues license tokens * **Resource Server** — serves the content and enforces access controls The flow follows a standard pattern: 1. The client discovers the publisher's `license.xml` (via `robots.txt`, a `Link` header, or an HTML `` tag) 2. The client reads the license, finds the content rule that matches the resource it wants to access, and identifies the license server from the `server` attribute 3. The client authenticates with the license server and requests a license token for that content 4. The license server validates the client's credentials, checks that an agreement exists, and returns a short-lived license token signed with the server's private key 5. The client attaches the token to content requests using `Authorization: License ` 6. The resource server (or CDN edge) validates the token signature against the license server's public keys and serves or rejects the request ```mermaid theme={null} sequenceDiagram participant Client as Crawler participant License as License Server participant Publisher as Publisher Client->>Publisher: Discover license.xml Publisher-->>Client: RSL license with server URL Client->>License: POST /token (credentials + license + resource) License->>License: Authenticate client, check agreement License-->>Client: Signed license token (JWT) Client->>Publisher: GET /content (Authorization: License ) Publisher->>Publisher: Verify JWT signature against JWKS Publisher-->>Client: 200 OK + Content ``` ## OLP endpoints The RSL specification defines three endpoints for a conformant license server: token acquisition, token introspection, and a key endpoint for encryption. Supertab Connect implements the token and introspection endpoints. The key endpoint is not yet supported. In addition, Supertab Connect exposes a JWKS (JSON Web Key Set) endpoint that publishes the public keys used to sign license tokens. This allows the CDN edge to verify tokens locally without calling the introspection endpoint on every request — which is how the Supertab Connect SDK performs verification by default. ### Token acquisition (`/token`) This is the primary endpoint. A client sends its credentials along with the license terms and the resource it wants to access. The server authenticates the client, verifies that a license agreement exists, and returns a signed JWT license token. The token endpoint URL comes from the `server` attribute on the matched `` element in the publisher's `license.xml`. The SDK posts to `{server}/token`. ```http theme={null} POST /token HTTP/1.1 Host: api-connect.supertab.co Content-Type: application/x-www-form-urlencoded Authorization: Basic grant_type=client_credentials &resource=/articles/* &license= ``` The `resource` is the URL pattern from the content rule. The `license` is the matched `` block from the publisher's `license.xml`. Client credentials are sent as HTTP Basic Authentication. A successful response: ```http theme={null} HTTP/1.1 200 OK Content-Type: application/json { "access_token": "eyJhbGciOiJFUzI1NiIs..." } ``` The `access_token` is a signed JWT that the client sends in the `Authorization` header on subsequent content requests using the `License` scheme — not `Bearer`. Supertab Connect signs the token with its private key using ES256. The token's `exp` claim determines its lifetime; the SDK caches and reuses it until close to expiry. ### Token introspection (`/introspect`) This endpoint lets a resource server verify whether a token is still valid and whether it grants access to a specific resource. It follows [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) (Token Introspection) with RSL-specific extensions. ```http theme={null} POST /introspect HTTP/1.1 Host: api-connect.supertab.co Content-Type: application/x-www-form-urlencoded token=eyJhbGciOiJFUzI1NiIs... &resource=/articles/* ``` The response includes whether the token is active and whether it permits the requested access: ```json theme={null} { "active": true, "token_type": "License", "license": "...", "resource": "/articles/*", "permitted": true, "reason": null } ``` When the token is invalid or inactive: ```json theme={null} { "active": false, "token_type": null, "license": null, "resource": "/articles/*", "permitted": false, "reason": "Invalid token claims" } ``` In practice, Supertab Connect's primary verification path does not use the introspection endpoint. Because license tokens are signed JWTs, the CDN edge can verify them locally by checking the signature against Supertab Connect's JWKS (JSON Web Key Set) endpoint — no per-request call to the license server needed. This is significantly faster and is how the Supertab Connect SDK performs verification. The introspection endpoint exists for cases where a resource server needs server-side validation or does not have access to a JWT verification library. ## Client authentication OLP requires client authentication on every token request. Clients register with Supertab Connect and receive a `client_id` and `client_secret`. When calling `obtainLicenseToken()` in the SDK, the client passes both credentials. The SDK sends them to the token endpoint as HTTP Basic Authentication (`Authorization: Basic base64(client_id:client_secret)`). ## How Supertab Connect implements OLP Supertab Connect operates as the license server. When a crawler calls `obtainLicenseToken()` in the SDK, the following happens under the hood: 1. The SDK fetches the publisher's `license.xml` and finds the content rule matching the requested resource 2. The SDK sends the matched license block and resource pattern to the token endpoint (derived from the `server` attribute in the content rule), authenticating with the client's credentials via HTTP Basic Auth 3. Supertab Connect authenticates the client, verifies that a license agreement exists between the crawler operator and the publisher, and issues a license token 4. The token is a JWT signed with Supertab Connect's private key (ES256), containing the client identity, the merchant system it is licensed against, and an expiry 5. The SDK caches the token and reuses it until close to expiry On the publisher side, token verification happens at the CDN edge. The Supertab Connect SDK fetches and caches the public keys from Supertab Connect's JWKS endpoint, then verifies the JWT signature and claims (expiry, audience, issuer) locally on every request. No round-trip to the license server is needed for verification, which keeps latency low at edge scale. ## What OLP does not do **OLP does not enforce access.** It issues tokens. Enforcement is handled by CAP at the resource server or CDN edge. A token acquired through OLP is useless without a resource server that checks it. **OLP does not negotiate terms.** The licensing terms are defined in the publisher's `license.xml`. OLP is the mechanism for requesting a token under those terms, not for changing them. **OLP does not handle payments directly.** Commercial terms (pricing, billing) are expressed in the RSL license and managed through the Supertab Connect platform. OLP's job is authentication and token issuance — the commercial relationship is established before the first token request. ## How OLP and CAP work together OLP and CAP are two halves of the same flow. OLP handles the "get a license" side, CAP handles the "prove you have a license" side. A crawler that has never interacted with a publisher will first receive a `401` from CAP with a `Link` header pointing to `license.xml`. The crawler reads the license, discovers the license server URL in the `server` attribute, authenticates via OLP, obtains a token, and retries the content request with the token attached. CAP then verifies the token's JWT signature against Supertab Connect's JWKS and allows the request through. Once the crawler has a cached token, subsequent requests skip the OLP flow entirely and go straight to the content request with CAP validation. ## Related Docs How CAP validates license tokens at the CDN edge. Practical guide to obtaining tokens and accessing licensed content. # Cloudflare Source: https://connect-docs.supertab.co/reference/cloudflare Publish RSL license and deploy CAP enforcement on Cloudflare Workers. Supertab Connect runs on Cloudflare as a single Worker that both serves your RSL license at `/license.xml` and enforces the Crawler Authentication Protocol (CAP) on all other traffic — everything stays on your domain. This flow uses the [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/). Install it and authenticate once before you start: ```bash theme={null} npx wrangler login ``` *** ## Project Setup ```bash theme={null} mkdir supertab-worker && cd supertab-worker npm init -y npm install @getsupertab/supertab-connect-sdk ``` ## Worker The Worker branches on the request path: `/license.xml` is proxied to the Supertab Connect API (keeping the URL on your domain), and every other request goes through CAP verification. ```typescript theme={null} // src/index.ts import { SupertabConnect, Env, EnforcementMode } from "@getsupertab/supertab-connect-sdk"; const MERCHANT_URN = "YOUR_WEBSITE_URN"; async function proxyLicenseXml(): Promise { const upstream = `https://api-connect.supertab.co/merchants/systems/${MERCHANT_URN}/license.xml`; const response = await fetch(upstream, { method: "GET", redirect: "manual" }); return new Response(response.body, { status: response.status, headers: response.headers, }); } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { const url = new URL(request.url); // Serve the RSL license — handled before the SDK is involved. if (url.pathname === "/license.xml") { return proxyLicenseXml(); } // Enforce CAP on everything else. return SupertabConnect.cloudflareHandleRequests(request, env, ctx, { enforcement: EnforcementMode.OBSERVE, analyticsEnabled: true, }); }, }; ``` Always pass `ctx` — the SDK uses its `waitUntil` to send events in the background without blocking the response: license-usage events whenever a token is verified, plus analytics events when `analyticsEnabled` is set. If your Worker isn't on your origin's hostname (for example, it proxies to a separate backend), pass an `originUrl` option so the SDK forwards allowed traffic there. Deployments using Workers Routes on your own domain can omit it — `fetch(request)` already resolves to your origin via Cloudflare's edge. ## Wrangler Configuration A single route sends all traffic on your domain to the Worker: ```jsonc theme={null} // wrangler.jsonc { "name": "supertab-worker", "main": "src/index.ts", "compatibility_date": "2025-05-21", "compatibility_flags": ["nodejs_compat"], "routes": [ { "pattern": "*yourdomain.com/*", "zone_id": "YOUR_ZONE_ID" } ] } ``` The `nodejs_compat` flag is required for the SDK to function. Find your `zone_id` in the Cloudflare dashboard under your domain → **Overview**, in the **API** section. ## API Key Secret Store your Merchant API key (from the Supertab Connect dashboard) as a Worker secret: ```bash theme={null} npx wrangler secret put MERCHANT_API_KEY ``` Paste the key when prompted. The SDK reads it automatically from the `env` object at runtime. ## Deploy ```bash theme={null} npx wrangler deploy ``` Use `wrangler dev` for local preview before deploying to production. ## Enforcement Modes Set `enforcement` in the handler options: | Mode | Behavior | | ------------------- | ------------------------------------------------------ | | `DISABLED` | Skip verification entirely — all requests pass through | | `OBSERVE` (default) | Verify tokens and record outcomes, but never block | | `ENFORCE` | Block requests with missing or invalid license tokens | Start in `OBSERVE` while you validate the integration, then move to `ENFORCE` when you're ready to block. ## Analytics & Bot Classification Analytics is **off by default**. Pass `analyticsEnabled: true` (shown above) to emit an event for every bot request the Worker sees. Events are sent to Supertab Connect in the background — no additional Cloudflare configuration or log streaming is required. These events are what power **bot classification** and traffic reporting in your Supertab Connect dashboard. Without `analyticsEnabled: true`, the Worker still enforces CAP, but records nothing — your dashboard shows no bot activity. ## Test Confirm the license is served — visit `https://yourdomain.com/license.xml`; you should see your RSL license with your domain in the URL bar. Confirm CAP is enforcing — visit `https://yourdomain.com` in your browser and you should see your normal homepage, unaffected. Then send a request with an invalid token: ```bash theme={null} curl https://yourdomain.com -H 'Authorization: License not-valid-token' ``` You should get a `401` invalid-token response, confirming CAP is verifying license tokens at the edge. ## Purge Cached License No action needed. Cloudflare serves the latest `license.xml` immediately after you publish a new version — there is no cache to invalidate. Confirm the update at `https://yourdomain.com/license.xml`. *** ## Related Docs CDN-agnostic guide covering RSL serving, CAP enforcement, and robots.txt. Generic CDN patterns for platforms not listed above. # CloudFront Source: https://connect-docs.supertab.co/reference/cloudfront Publish RSL license and deploy CAP enforcement on AWS CloudFront. Supertab Connect integrates with AWS CloudFront for two purposes: serving your RSL license at `/license.xml` via your domain, and enforcing the Crawler Authentication Protocol (CAP) using Lambda\@Edge and CloudFront Functions. *** ## Publishing RSL License Your RSL license needs to be accessible at `https://yourdomain.com/license.xml`. CloudFront proxies this path to the Supertab Connect origin using a CloudFront Function for URI rewriting, a new origin, and a dedicated cache behavior. ### CloudFront Function Create a function with runtime `cloudfront-js-2.0`. This runs on viewer request and rewrites the URI before CloudFront selects the origin. ```javascript theme={null} function handler(event) { var request = event.request; var merchantURN = "YOUR_WEBSITE_URN"; request.uri = "/merchants/systems/" + merchantURN + request.uri; return request; } ``` Publish the function after saving. ### Origin Add an origin to your distribution: ``` Origin domain: api-connect.supertab.co Protocol: HTTPS only Name: supertab-connect-origin ``` ### Cache Behavior Create a cache behavior for `/license.xml`: | Setting | Value | | ----------------------- | ---------------------------------- | | Path pattern | `/license.xml` | | Origin | `supertab-connect-origin` | | Viewer protocol policy | Redirect HTTP to HTTPS | | Cache policy | CachingOptimized | | Origin request policy | `AllViewerExceptHostHeader` | | Viewer request function | Your published CloudFront Function | Use `AllViewerExceptHostHeader`, not `AllViewer`. The SDK needs viewer headers like `User-Agent` for bot detection, but forwarding the `Host` header causes origin routing failures. The `/license.xml` behavior must sit above the default `*` behavior in the behaviors list. Deployment takes 10–15 minutes after saving. If you manage your distribution with Terraform, use this configuration instead of the manual steps above. ```hcl theme={null} resource "aws_cloudfront_function" "supertab_rewrite_license_path" { name = "supertab-rewrite-license-path" runtime = "cloudfront-js-2.0" publish = true comment = "Rewrites /license.xml to the Supertab Connect URN path" code = <<-EOT function handler(event) { var request = event.request; var merchantURN = "YOUR_WEBSITE_URN"; request.uri = "/merchants/systems/" + merchantURN + request.uri; return request; } EOT } resource "aws_cloudfront_distribution" "your_distribution" { # ... your existing config ... origin { domain_name = "api-connect.supertab.co" origin_id = "supertab-connect-origin" custom_origin_config { http_port = 80 https_port = 443 origin_protocol_policy = "https-only" origin_ssl_protocols = ["TLSv1.2"] } } ordered_cache_behavior { path_pattern = "/license.xml" allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] target_origin_id = "supertab-connect-origin" forwarded_values { query_string = false headers = ["Origin"] cookies { forward = "none" } } function_association { event_type = "viewer-request" function_arn = aws_cloudfront_function.supertab_rewrite_license_path.arn } viewer_protocol_policy = "redirect-to-https" min_ttl = 0 default_ttl = 86400 max_ttl = 31536000 compress = true } depends_on = [aws_cloudfront_function.supertab_rewrite_license_path] } ``` *** ## CAP Enforcement CloudFront CAP deployment combines two edge features: * **CloudFront Function** (viewer request): Identifies `Authorization: License` headers on every request, including cache hits, without adding latency for regular traffic. * **Lambda\@Edge** (origin request): Runs the Supertab Connect SDK to verify the token and record usage. Fires only on cache misses. CloudFront Functions fire on every request including cache hits. Lambda\@Edge origin-request fires only on cache misses. This distinction matters for billing and completeness of usage recording. Lambda\@Edge functions must be deployed in **us-east-1**. ### Prerequisites You need Node.js 22+, npm, and the AWS CLI configured (`aws configure` or `aws login`). If you have `PowerUserAccess`, that covers all IAM requirements. Otherwise, the deploying user needs a scoped policy. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "CLILogin", "Effect": "Allow", "Action": [ "signin:AuthorizeOAuth2Access", "signin:CreateOAuth2Token" ], "Resource": "*" }, { "Sid": "IAMRole", "Effect": "Allow", "Action": [ "iam:CreateRole", "iam:GetRole", "iam:AttachRolePolicy", "iam:PassRole" ], "Resource": "arn:aws:iam::*:role/supertab-edge-verify*" }, { "Sid": "AllowServiceLinkedRoleForLambdaEdge", "Effect": "Allow", "Action": "iam:CreateServiceLinkedRole", "Resource": "arn:aws:iam::*:role/aws-service-role/replicator.lambda.amazonaws.com/*", "Condition": { "StringEquals": { "iam:AWSServiceName": "replicator.lambda.amazonaws.com" } } }, { "Sid": "LambdaEdge", "Effect": "Allow", "Action": [ "lambda:CreateFunction", "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration", "lambda:GetFunction", "lambda:GetFunctionConfiguration", "lambda:PublishVersion", "lambda:AddPermission", "lambda:EnableReplication*" ], "Resource": "arn:aws:lambda:us-east-1:*:function:supertab-verify*" }, { "Sid": "CloudFrontFunctionCreate", "Effect": "Allow", "Action": ["cloudfront:CreateFunction", "cloudfront:ListFunctions"], "Resource": "*" }, { "Sid": "CloudFrontFunctionManage", "Effect": "Allow", "Action": ["cloudfront:GetFunction", "cloudfront:DescribeFunction", "cloudfront:UpdateFunction", "cloudfront:PublishFunction"], "Resource": "arn:aws:cloudfront::*:function/supertab-*" }, { "Sid": "CloudFrontCachePolicyManage", "Effect": "Allow", "Action": ["cloudfront:ListCachePolicies", "cloudfront:CreateCachePolicy"], "Resource": "*" }, { "Sid": "CloudFrontDistributionManage", "Effect": "Allow", "Action": [ "cloudfront:GetDistribution", "cloudfront:GetDistributionConfig", "cloudfront:UpdateDistribution" ], "Resource": "arn:aws:cloudfront::*:distribution/YOUR_DISTRIBUTION_ID" } ] } ``` ### Step 1: Build the Lambda Package ```bash theme={null} mkdir supertab-verify && cd supertab-verify npm init -y npm install @getsupertab/supertab-connect-sdk npm install -D esbuild typescript @types/aws-lambda ``` Create `index.ts`: ```typescript theme={null} import { SupertabConnect } from "@getsupertab/supertab-connect-sdk"; import type { CloudFrontRequestEvent, CloudFrontRequestResult } from "aws-lambda"; export async function handler( event: CloudFrontRequestEvent ): Promise { return SupertabConnect.cloudfrontHandleRequests(event, { apiKey: "YOUR_MERCHANT_API_KEY", // from your Supertab Connect dashboard }); } ``` Add build scripts to `package.json`: ```json theme={null} { "scripts": { "build": "esbuild index.ts --bundle --platform=node --target=node22 --outfile=dist/index.js --format=cjs", "package": "cd dist && zip -r function.zip index.js", "bundle": "npm run build && npm run package" } } ``` Build: ```bash theme={null} npm run bundle ``` This produces `dist/function.zip`. ### Step 2: Deploy to AWS Deployment creates three resources: an **IAM execution role** (assumable by both `lambda.amazonaws.com` and `edgelambda.amazonaws.com`, with `AWSLambdaBasicExecutionRole` for CloudWatch logging), the **Lambda function** in `us-east-1` (required for Lambda\@Edge — CloudFront replicates it globally from there), and a **published, numbered version** that CloudFront is granted permission to invoke at the edge. Lambda\@Edge cannot use `$LATEST`. The script below does all three and is idempotent — re-run it after any handler change and it publishes a new version. Save it as `deploy.sh` in your `supertab-verify` directory. ```bash theme={null} #!/bin/bash FUNCTION_NAME="supertab-verify" ROLE_NAME="supertab-edge-verify" ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) if [ -z "$ACCOUNT_ID" ]; then echo "❌ AWS CLI not configured. Run 'aws configure' first." exit 1 fi echo "" echo "=== Deploying Lambda@Edge (Account: $ACCOUNT_ID) ===" echo "" # 1. Create execution role echo "Step 1/4 — Creating IAM role..." cat > /tmp/trust-policy.json << 'EOF' { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] }, "Action": "sts:AssumeRole" }] } EOF if aws iam create-role --role-name $ROLE_NAME \ --assume-role-policy-document file:///tmp/trust-policy.json; then echo " ✅ Created role: $ROLE_NAME" else if aws iam get-role --role-name $ROLE_NAME > /dev/null 2>&1; then echo " ✅ Role already exists: $ROLE_NAME" else echo " ❌ Failed to create role" exit 1 fi fi echo " ⏳ Attaching execution policy..." aws iam attach-role-policy --role-name $ROLE_NAME \ --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole || true echo " ✅ Logging permissions attached" echo " ⏳ Waiting for IAM propagation..." sleep 10 # 2. Create or update Lambda function (must be us-east-1) echo "Step 2/4 — Deploying function..." if aws lambda get-function --function-name $FUNCTION_NAME --region us-east-1 > /dev/null 2>&1; then echo " ℹ️ Function exists, updating..." aws lambda update-function-code --function-name $FUNCTION_NAME \ --zip-file fileb://dist/function.zip --region us-east-1 || { echo " ❌ Failed to update function" exit 1 } aws lambda wait function-updated --function-name $FUNCTION_NAME --region us-east-1 || { echo " ❌ Failed waiting for function update" exit 1 } echo " ✅ Code updated" else echo " ℹ️ Creating new function..." aws lambda create-function --function-name $FUNCTION_NAME --runtime nodejs22.x \ --role arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME} --handler index.handler \ --zip-file fileb://dist/function.zip --timeout 10 --memory-size 128 \ --region us-east-1 || { echo " ❌ Failed to create function" exit 1 } echo " ✅ Function created" fi echo " ⏳ Waiting for function to be ready..." sleep 5 # 3. Publish version (Lambda@Edge requires numbered versions, not $LATEST) echo "Step 3/4 — Publishing version..." VERSION=$(aws lambda publish-version --function-name $FUNCTION_NAME \ --region us-east-1 --query 'Version' --output text) || { echo " ❌ Failed to publish version" exit 1 } echo " ✅ Published version: $VERSION" # 4. Grant CloudFront permission to invoke at edge locations echo "Step 4/4 — Granting edge permissions..." aws lambda add-permission --function-name ${FUNCTION_NAME}:${VERSION} \ --statement-id "cf-get-${VERSION}" --action lambda:GetFunction \ --principal edgelambda.amazonaws.com --region us-east-1 || true aws lambda add-permission --function-name ${FUNCTION_NAME}:${VERSION} \ --statement-id "cf-invoke-${VERSION}" --action lambda:InvokeFunction \ --principal edgelambda.amazonaws.com --region us-east-1 || true echo " ✅ CloudFront can invoke version $VERSION" # Done ARN="arn:aws:lambda:us-east-1:${ACCOUNT_ID}:function:${FUNCTION_NAME}:${VERSION}" echo "" echo "=== ✅ Done ===" echo "Function ARN (copy this for Step 4):" echo " $ARN" echo "" ``` Run it: ```bash theme={null} chmod +x deploy.sh ./deploy.sh ``` Save the version ARN from the output — you need it in Step 4. Prefer the console? Create the role with the Lambda + `edgelambda` trust policy (shown in the script above), create the function (`nodejs22.x`, `us-east-1`, handler `index.handler`, timeout 10s, memory 128 MB), upload `dist/function.zip`, publish a numbered version, then grant `lambda:GetFunction` and `lambda:InvokeFunction` to the `edgelambda.amazonaws.com` principal on that version. If you update your handler later, publish a new version and repoint the behavior to the new ARN. ### Step 3: Create the Filtering Function Create a CloudFront Function (`cloudfront-js-2.0` runtime) that runs on viewer request. It checks whether the `Authorization` header contains a license token. If it does, it adds `x-license-auth` to the request headers — this header is used in the cache key to separate licensed and unlicensed cache entries, and its presence triggers the Lambda\@Edge function on cache misses. ```javascript theme={null} function isLicenseRequest(request) { var authHeader = (request.headers || {}).authorization; var authValue = authHeader && authHeader.value ? authHeader.value : ""; return authValue.length > 7 && authValue.slice(0, 8).toLowerCase() === "license "; } function handler(event) { var request = event.request; var headers = request.headers || {}; if (isLicenseRequest(request)) { request.headers["x-license-auth"] = { value: event.context.requestId }; request.headers["x-original-request-url"] = { value: (headers.host && headers.host.value ? headers.host.value : "") + request.uri, }; } return request; } ``` Publish the function after saving. ### Step 4: Configure the Cache Behavior Edit the behavior for the path you want to protect (the default `*` behavior for all requests, or a specific path like `/articles/*`). **Cache policy** — Create a custom cache policy with `x-license-auth` included in the cache key headers. This ensures that requests with and without a license token produce separate cache entries — without it, a cached response from a licensed request could be served to unlicensed requests. Keep defaults for TTL, query strings, cookies, and compression. **Origin request policy** — Set to `AllViewerExceptHostHeader`. This forwards viewer headers (including `User-Agent` for bot detection) to the origin while letting CloudFront set the correct origin hostname. Do not use `AllViewer` — it forwards the original `Host` header, which causes routing failures on S3 and API Gateway origins. **Function associations:** | Event | Type | Value | | -------------- | -------------------- | --------------------------------------------- | | Viewer request | CloudFront Functions | Your published filtering function from Step 3 | | Origin request | Lambda\@Edge | Published version ARN from Step 2 | Save and wait for the distribution to deploy (10–15 minutes). *** ## Purge Cached License After you publish a new license version, CloudFront may keep serving the cached copy for up to 24 hours. Invalidate the license path to force a refresh. In the console, open your distribution → **Invalidations** → **Create invalidation**, and enter: ``` /merchants/systems/YOUR_WEBSITE_URN/license.xml ``` Or via the CLI: ```bash theme={null} aws cloudfront create-invalidation \ --distribution-id YOUR_DISTRIBUTION_ID \ --paths "/merchants/systems/YOUR_WEBSITE_URN/license.xml" ``` Invalidation completes in 5–15 minutes. Confirm the update at `https://yourdomain.com/license.xml`. *** ## Related Docs CDN-agnostic guide covering RSL serving, CAP enforcement, and robots.txt. Generic CDN patterns for platforms not listed above. # Fastly Source: https://connect-docs.supertab.co/reference/fastly/connect-on-fastly Serve your RSL license and enforce CAP on Fastly — as a single Compute service, or by chaining an existing VCL service to a Compute validator. Supertab Connect serves your RSL license at `/license.xml` and enforces the Crawler Authentication Protocol (CAP) on crawler requests. The SDK runs in a Fastly **Compute** service, so how you deploy depends on your current Fastly setup. | Your setup | Approach | What you do | | :----------------------------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------ | | **Compute** (greenfield or full migration) | [Compute service](#compute-service) | One SDK handler serves `/license.xml` and enforces CAP. | | **Existing VCL service** | [VCL and Compute (chaining)](#vcl-and-compute-chaining) | Keep your VCL service and chain licensed requests to a Compute validator. | CAP enforcement always requires a Compute service — the SDK is Wasm and does not run in VCL. A pure-VCL service can still *serve* the RSL license via a URL rewrite (see [Serving the license on VCL](#serving-the-license-on-vcl)), but it cannot enforce CAP on its own. *** ## Compute service Everything runs in one Compute service: the SDK serves `/license.xml` (via `enableRSL`) and enforces CAP on all other traffic. Install the SDK: ```bash theme={null} npm install @getsupertab/supertab-connect-sdk ``` ### Backends The Compute service needs two backends: * **`stc-backend`** → `api-connect.supertab.co:443` (TLS enabled). The SDK routes its own calls to Supertab Connect — JWKS, token verification, events, and the RSL license fetch — through a backend that must be named exactly `stc-backend`, or those requests fail with a `502`. ``` Name: stc-backend Address: api-connect.supertab.co Port: 443 TLS: enabled SNI hostname: api-connect.supertab.co Certificate hostname: api-connect.supertab.co Override host: api-connect.supertab.co ``` * **Your content origin** (e.g. `content_origin`) → your site. Allowed traffic is forwarded here; pass its name as the third argument to the handler. ### Secret Store Create a Fastly Secret Store named `supertab_config`, containing `MERCHANT_API_KEY` (from your Supertab Connect dashboard), and link it to the Compute service. ### Handler ```javascript theme={null} /// import { SupertabConnect, EnforcementMode } from "@getsupertab/supertab-connect-sdk"; import { SecretStore } from "fastly:secret-store"; const secrets = new SecretStore("supertab_config"); const merchantApiKey = (await secrets.get("MERCHANT_API_KEY")).plaintext(); addEventListener("fetch", (event) => { event.respondWith( SupertabConnect.fastlyHandleRequests( event, merchantApiKey, "content_origin", { enableRSL: true, // serve /license.xml from the SDK merchantSystemUrn: "YOUR_WEBSITE_URN", enforcement: EnforcementMode.OBSERVE, // see Enforcement modes analyticsEnabled: true, // see Bot-event logging logEndpoint: "bot_events", } ) ); }); ``` Pass the `event` (the Fastly `FetchEvent`), not `event.request` — the SDK reads the request, client signals, and `waitUntil` from it. With `enableRSL: true`, `/license.xml` is handled internally; every other request goes through CAP. ### Enforcement modes Set `enforcement` in the options: | Mode | Behavior | | :------------------ | :----------------------------------------------------- | | `DISABLED` | Skip verification entirely — all requests pass through | | `OBSERVE` (default) | Verify tokens and record outcomes, but never block | | `ENFORCE` | Block requests with missing or invalid license tokens | Start in `OBSERVE` while you validate the integration, then move to `ENFORCE` when you're ready to block. ### Bot detection By default the SDK identifies known crawlers by their user agent. Pass a `botDetector` function to extend or override this logic. ```javascript theme={null} const isBot = (request) => { const ua = request.headers.get("User-Agent") || ""; return ua.includes("MyCustomBot") || ua.includes("Scraper"); }; SupertabConnect.fastlyHandleRequests(event, merchantApiKey, "content_origin", { botDetector: isBot, enforcement: EnforcementMode.ENFORCE, }); ``` *** ## VCL and Compute (chaining) Use this when you already run a VCL service and only want licensed requests to detour through Compute. The VCL service detects the `Authorization: License` header and chains those requests to a Compute validator, which runs the SDK and forwards to your normal origin. Everything else stays on your existing CDN path. Because only licensed requests are chained, `/license.xml` never reaches Compute — serve it from the VCL layer (see [Serving the license on VCL](#serving-the-license-on-vcl)). ### Compute validator service The validator runs the same SDK handler as a standalone Compute service. `enableRSL` is omitted here (the license is served on VCL); it accepts the same `enforcement`, `botDetector`, and analytics options shown in the [Compute service](#compute-service) section. ```javascript theme={null} /// import { SupertabConnect } from "@getsupertab/supertab-connect-sdk"; import { SecretStore } from "fastly:secret-store"; addEventListener("fetch", (event) => { event.respondWith((async () => { const secrets = new SecretStore("supertab_config"); const merchantApiKey = (await secrets.get("MERCHANT_API_KEY")).plaintext(); return SupertabConnect.fastlyHandleRequests( event, merchantApiKey, "content_origin" ); })()); }); ``` It requires: * A Secret Store called `supertab_config` containing `MERCHANT_API_KEY`, linked to the Compute service. * A backend for your real origin, passed as the third argument (`content_origin`). * A backend named exactly `stc-backend` → `api-connect.supertab.co:443` (same host/TLS settings as in [Backends](#backends) above) for the SDK's own Supertab calls. ### VCL snippets — `vcl_recv` and `vcl_pass` On your VCL service, add a `recv` snippet to reroute licensed requests to the Compute validator: ```vcl theme={null} if (req.http.Authorization ~ "^License ") { set req.backend = F_supertab_compute_validator; return (pass); } ``` `F_supertab_compute_validator` refers to a host/backend named `supertab-compute-validator` that you define in your VCL service, pointing at the Compute service's autogenerated domain. Configure it with TLS enabled and the edgecompute domain set as the SNI, certificate, and override host — otherwise the CDN → Compute hop fails: ``` Name: supertab-compute-validator Address: .edgecompute.app Port: 443 TLS: enabled SNI hostname: .edgecompute.app Certificate hostname: .edgecompute.app Override host: .edgecompute.app ``` Then add a `pass` snippet so the original request URL reaches Compute: ```vcl theme={null} declare local var.scheme STRING; if (req.is_ssl) { set var.scheme = "https"; } else { set var.scheme = "http"; } set bereq.http.X-Original-Request-Url = var.scheme "://" req.http.host req.url; ``` `X-Original-Request-Url` is used to verify the license token's `aud` claim. Without it, CAP fails with an `insufficient_scope` error because the SDK can't confirm all properties required by the RSL spec. **Note:** keep the rest of your VCL flow intact so non-licensed traffic never leaves the CDN path. If the SDK rejects a token with an audience or scope error, confirm the `pass` snippet that sets `X-Original-Request-Url` runs before the request reaches Compute. *** ## Serving the license on VCL On a VCL service, `/license.xml` is served by proxying to the Supertab Connect origin and rewriting the short path to the full URN path — the SDK is not involved. (Compute services do this via `enableRSL` instead.) ### Backend Add a host pointing to the Supertab Connect origin: ``` Name: supertab-connect-backend Address: api-connect.supertab.co Port: 443 TLS: enabled SNI hostname: api-connect.supertab.co Certificate hostname: api-connect.supertab.co Override host: api-connect.supertab.co ``` ### Condition Attach a request condition to `supertab-connect-backend`: ```vcl theme={null} req.url ~ "^/merchants/systems/YOUR_WEBSITE_URN/license\.xml(\?|$)" ``` ### VCL Snippet Add a `recv` snippet at priority 100: ```vcl theme={null} if (req.url.path == "/license.xml") { set req.url = "/merchants/systems/YOUR_WEBSITE_URN/license.xml"; } ``` This rewrites the short URL before the condition runs, so the backend condition matches and the request is routed to `api-connect.supertab.co`. Activate the new version once the backend, condition, and snippet are in place. *** ## Bot-Event Logging When the SDK runs in a Compute service, it can emit one analytics event per request to a Fastly logging endpoint named `bot_events`. Supertab loads those events into your bot-traffic analytics. This is the recommended path: every request flows through Compute, so a Fastly log-streaming endpoint handles that volume without adding an outbound request per hit. Omitting `logEndpoint` falls back to Supertab Connect's HTTP relay instead of S3 log streaming. That's fine for low volume, but on Fastly Compute the relay needs `stc-backend` to reach Supertab and adds an outbound request per hit — prefer the `bot_events` endpoint below for production traffic. ### Enable analytics in the SDK Pass the analytics options to `fastlyHandleRequests`: ```javascript theme={null} SupertabConnect.fastlyHandleRequests( event, merchantApiKey, "content_origin", { analyticsEnabled: true, logEndpoint: "bot_events", merchantSystemUrn: "YOUR_WEBSITE_URN", } ); ``` You can find your Merchant System URN on the **View Website Details** page of the Merchant Portal, in the same place as your API keys. If you prefer not to hardcode it, store it in your Secret Store alongside `MERCHANT_API_KEY` (e.g. as `MERCHANT_SYSTEM_URN`) and read it the same way. ### Create the S3 logging endpoint The SDK writes events to a log streaming endpoint that must exist on your Compute service. In the Fastly dashboard, go to **Resources** → **Log streaming** → **Create endpoint** → **Amazon S3** and set: | Setting | Value | | :-------------------------- | :------------------------------------------------------ | | **Name** | `bot_events` | | **Bucket name** | `lpeu-prod-connect-bot-events` | | **Path** | `bot-events/` | | **Domain** | `s3.eu-central-1.amazonaws.com` (region `eu-central-1`) | | **Access key / Secret key** | credentials provided by Supertab | | **Log format** | **Blank** | | **Compression** | none | | **Period** | `900` seconds (15 min) | A few of these are easy to get wrong: * The endpoint **name must be exactly `bot_events`**, matching the `logEndpoint` SDK option — otherwise the logs are silently dropped. * The **log format must be Blank**. The SDK already writes one JSON object per line; the default (**Classic**) prepends a syslog header and corrupts every event. * **Leave gzip compression off**, or the Supertab connector won't match the `*.log` objects. * The **regional domain** `s3.eu-central-1.amazonaws.com` is required because the bucket is not in `us-east-1`. Save the endpoint and activate the service version to start shipping events. *** ## Purge cached license When you publish a new license version, Fastly keeps serving the cached `license.xml` until it's purged. Purge that single URL to force a refresh: * **VCL service:** dashboard → your service → **Purge** → enter `https://yourdomain.com/license.xml` → **Purge**. * **Compute service:** dashboard → **Compute** → **Services** → your service → **Purge** → enter `https://yourdomain.com/license.xml` → **Purge**. Confirm the update at `https://yourdomain.com/license.xml`. *** ## Manual verification For fine-grained control on either Compute deployment, use `verifyAndRecord()` on a `SupertabConnect` instance instead of `fastlyHandleRequests`. ```javascript theme={null} const supertab = new SupertabConnect({ apiKey: merchantApiKey }); const result = await supertab.verifyAndRecord({ token: licenseToken, resourceUrl: request.url, userAgent: request.headers.get("User-Agent"), }); if (result.valid) { // forward to origin } ``` *** ## Related Docs CDN-agnostic guide covering RSL serving, CAP enforcement, and robots.txt. Generic CDN patterns for platforms not listed above. # Other CDNs Source: https://connect-docs.supertab.co/reference/others Manual SDK integration for CDN platforms without a dedicated reference page. If your CDN is not Fastly, CloudFront, or Cloudflare, use the generic SDK API to build your own integration. The [Deploy in Your CDN](/guides/deploy-cdn) guide covers the general pattern for RSL license serving — adding an origin, routing `/license.xml`, and rewriting the URL. This page focuses on CAP enforcement using the SDK directly. *** ## SDK Setup ```bash theme={null} npm install @getsupertab/supertab-connect-sdk ``` ```javascript theme={null} import { SupertabConnect, EnforcementMode } from "@getsupertab/supertab-connect-sdk"; const supertab = new SupertabConnect({ apiKey: "YOUR_MERCHANT_API_KEY", enforcement: EnforcementMode.OBSERVE, // DISABLED | OBSERVE (default) | ENFORCE analyticsEnabled: true, // emit events for bot classification }); ``` Bot detection is configured at construction time. Pass a `botDetector` function to extend or override the built-in user-agent heuristics: ```javascript theme={null} const supertab = new SupertabConnect({ apiKey: "YOUR_MERCHANT_API_KEY", analyticsEnabled: true, botDetector: (request) => { const ua = request.headers.get("User-Agent") || ""; // Add your CDN-specific signals or custom logic here return ua.includes("MyBot"); }, }); ``` Analytics is **off by default**. Set `analyticsEnabled: true` to emit one event per request to Supertab Connect — this is what powers bot classification and traffic reporting in your dashboard. Enforcement is separate: it decides whether unlicensed requests are allowed or blocked. *** ## Option A: `handleRequest` (Recommended) The `handleRequest` method handles the full lifecycle — bot detection, token extraction, verification, enforcement, and analytics (when enabled) — in one call. It returns the correct `401`/`403` response with the `WWW-Authenticate` and `Link` headers automatically. Pass an `ExecutionContext` via the `ctx` option if your runtime supports background tasks, so event recording never blocks the response. ```javascript theme={null} async function handler(request, ctx) { return supertab.handleRequest(request, { ctx }); } ``` *** ## Option B: Manual Verification For fine-grained control over responses or custom routing, use `verifyAndRecord` directly: ```javascript theme={null} async function handler(request) { // Extract the license token const auth = request.headers.get("Authorization") || ""; const token = auth.startsWith("License ") ? auth.slice(8) : ""; const LICENSE_URL = "https://yourdomain.com/license.xml"; if (!token) { return new Response("License token required", { status: 401, headers: { "WWW-Authenticate": `License error="invalid_request", error_description="A license token is required"`, Link: `<${LICENSE_URL}>; rel="license"; type="application/rsl+xml"`, }, }); } const result = await supertab.verifyAndRecord({ token, resourceUrl: request.url, userAgent: request.headers.get("User-Agent"), }); if (!result.valid) { return new Response(`Access denied: ${result.error}`, { status: 401, headers: { "WWW-Authenticate": `License error="invalid_token", error_description="${result.error}"`, Link: `<${LICENSE_URL}>; rel="license"; type="application/rsl+xml"`, }, }); } return fetch(request); } ``` The `WWW-Authenticate` and `Link` headers in the 401 responses follow the CAP specification and tell the crawler where to obtain a license. *** ## Related Docs General deployment guide covering RSL serving, CAP, and robots.txt. Full API reference for all SDK methods across languages. # SDKs Source: https://connect-docs.supertab.co/reference/sdk Available SDKs, what they cover, and how to pick the right one for your integration. Supertab Connect SDKs handle two sides of the licensing flow: crawler operators use them to obtain license tokens for protected content, and publishers use them to verify tokens and enforce access at the edge. ## SDK Availability | Language | Package | Install | Token acquisition | Verification & enforcement | CDN runtime helpers | | ---------- | ----------------------------------- | -------- | ----------------- | -------------------------- | ---------------------------------------------- | | TypeScript | `@getsupertab/supertab-connect-sdk` | npm | Yes | Yes | Cloudflare Workers, Fastly Compute, CloudFront | | PHP | `getsupertab/connect-sdk-php` | Composer | Yes | Yes | — | | Python | `supertab-connect-sdk` | pip | Yes | Yes | — | ## Choosing the Right API | You want to... | Use | | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | Acquire a token before requesting protected content | `obtainLicenseToken()` | | Check a token without side effects | `verify()` | | Check a token and record usage for analytics/billing | `verifyAndRecord()` | | Protect a route or origin request end-to-end | `handleRequest()` | | Integrate at a supported CDN edge with minimal boilerplate | CDN runtime helper (`cloudflareHandleRequests`, `fastlyHandleRequests`, `cloudfrontHandleRequests`) | Static methods like `obtainLicenseToken()` and `verify()` can be called without initializing a client. Methods that record events or enforce access (`verifyAndRecord`, `handleRequest`, runtime helpers) require a configured client instance with your API key. ## Enforcement Modes Publisher-side request handling uses an enforcement mode to control what happens when automated traffic reaches protected content without a valid token. | Mode | Behavior | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `OBSERVE` (default) | Requests pass through, but the SDK verifies tokens, records outcomes, and attaches licensing headers. Use this during initial rollout. | | `ENFORCE` | Bots without a valid token are blocked with a `401` (or `403` for a valid token that doesn't cover the resource) and licensing headers. | | `DISABLED` | No verification. Requests are allowed without licensing intervention. | ## Language-Specific References Each language reference covers installation, configuration, full method signatures, and code examples. Edge-ready SDK for Cloudflare Workers, Fastly Compute, and CloudFront Lambda\@Edge. Server-side SDK for WordPress and PHP applications. Async server-side SDK for Python applications and crawler clients. # PHP SDK Source: https://connect-docs.supertab.co/reference/sdk/php The Supertab Connect PHP SDK lets publishers implement RSL license serving and CAP enforcement in PHP applications. It handles token verification, bot detection, enforcement, and — when enabled — analytics for agent & bot classification. **Requirements:** PHP 8.1+, with extensions `ext-curl`, `ext-json`, `ext-openssl`, `ext-simplexml`. ## Installation ```bash theme={null} composer require getsupertab/connect-sdk-php ``` ## Initializing the Client ```php theme={null} use Supertab\Connect\SupertabConnect; use Supertab\Connect\Enum\EnforcementMode; $connect = new SupertabConnect( apiKey: 'stc_live_your_api_key', // from your Supertab Connect dashboard enforcement: EnforcementMode::OBSERVE, // default analyticsEnabled: true, // emit events for bot classification (off by default) ); ``` ### Configuration Options | Parameter | Type | Required | Default | Description | | ------------------ | ----------------------- | -------- | --------- | ------------------------------------------------------ | | `apiKey` | `string` | Yes | — | Merchant API key (`stc_live_...` or `stc_sandbox_...`) | | `enforcement` | `EnforcementMode` | No | `OBSERVE` | How to handle bots without a valid token | | `analyticsEnabled` | `bool` | No | `false` | Emit per-request events for agent & bot classification | | `debug` | `bool` | No | `false` | Enables verbose logging via `error_log()` | | `botDetector` | `?BotDetectorInterface` | No | `null` | Custom bot detection logic | | `httpClient` | `?HttpClientInterface` | No | `null` | Custom HTTP client | The SDK enforces a singleton pattern per API key. Instantiating with a different key throws an exception. Use `SupertabConnect::resetInstance()` if you need to change configuration. ## Common Workflows ### Handle a Protected Request `handleRequest()` manages the full lifecycle — token extraction, verification, bot detection, enforcement, and event recording (usage always; analytics when `analyticsEnabled` is set). By default it reads from `$_SERVER`. ```php theme={null} use Supertab\Connect\SupertabConnect; use Supertab\Connect\Enum\EnforcementMode; use Supertab\Connect\Result\BlockResult; $connect = new SupertabConnect( apiKey: 'stc_live_your_api_key', enforcement: EnforcementMode::ENFORCE, ); $result = $connect->handleRequest(); foreach ($result->headers as $name => $value) { header("{$name}: {$value}"); } if ($result instanceof BlockResult) { http_response_code($result->status); echo $result->body; exit; } // Serve content for allowed requests ``` ### Framework Integration Pass a `RequestContext` instead of relying on `$_SERVER` when using a framework: ```php theme={null} use Supertab\Connect\Http\RequestContext; $ctx = new RequestContext( url: $request->getUri(), authorizationHeader: $request->header('Authorization'), userAgent: $request->header('User-Agent'), accept: $request->header('Accept'), acceptLanguage: $request->header('Accept-Language'), secChUa: $request->header('Sec-CH-UA'), ); $result = $connect->handleRequest($ctx); ``` ### Verify a Token and Record Usage `verifyAndRecord()` verifies a token and records a usage event for billing and reporting. ```php theme={null} $connect = new SupertabConnect(apiKey: 'stc_live_your_api_key'); $result = $connect->verifyAndRecord( token: $token, resourceUrl: 'https://example.com/article/my-slug', userAgent: $_SERVER['HTTP_USER_AGENT'] ?? null, ); if (!$result->valid) { http_response_code(401); echo $result->error; exit; } ``` ### Verify Without Recording Use the static `verify()` for a lightweight validity check with no analytics side effects. ```php theme={null} $result = SupertabConnect::verify( token: $token, resourceUrl: 'https://example.com/article/my-slug', ); if (!$result->valid) { http_response_code(401); echo $result->error; exit; } ``` ### Obtain a License Token Use `obtainLicenseToken()` to acquire a token before requesting licensed content. The SDK fetches and parses the publisher's `license.xml`, matches the resource URL to a content rule, and exchanges your credentials for a token. Tokens are cached in memory and refreshed automatically before expiry. ```php theme={null} $token = SupertabConnect::obtainLicenseToken( clientId: 'your_client_id', clientSecret: 'your_client_secret', resourceUrl: 'https://example.com/article/my-slug', ); $ch = curl_init('https://example.com/article/my-slug'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: License {$token}"], ]); $response = curl_exec($ch); ``` ### Serve the RSL License Use `fetchLicenseXml()` to proxy your RSL license from Supertab Connect and serve it at `/license.xml` on your domain. ```php theme={null} $xml = SupertabConnect::fetchLicenseXml( merchantSystemUrn: 'urn:stc:merchant:system:your_system_id', ); header('Content-Type: application/rsl+xml'); echo $xml; ``` ## Important Types ### `EnforcementMode` Enforcement modes determine what happens to a **bot** request. Non-bot traffic is always allowed. * `DISABLED`: No verification — every request passes through untouched. * `OBSERVE` (Default): Tokens are verified and outcomes recorded. A bot with **no** token is allowed through with RSL signaling headers (`Link`, `X-RSL-Status`) indicating a license is required; a bot presenting an **invalid** token is still blocked. * `ENFORCE`: Blocks any bot without a valid token — `401 Unauthorized` (missing or invalid token) or `403 Forbidden` (token valid but wrong audience). Invalid tokens are always blocked except in `DISABLED` mode. ### `HandlerResult` Returned by `handleRequest()`. Has two subtypes: * **`AllowResult`** — `action: ALLOW`, plus `headers` to set on the response * **`BlockResult`** — `action: BLOCK`, plus `status` (HTTP code), `body`, and `headers` ### `VerificationResult` Returned by `verify()` and `verifyAndRecord()`: | Field | Type | Description | | ------- | --------- | ----------------------------------------------------- | | `valid` | `bool` | Whether the token is valid for the requested resource | | `error` | `?string` | Human-readable error message when invalid | ## Tips **Always apply response headers.** The SDK returns `Link` and `X-RSL-Status` headers even on allowed requests in OBSERVE mode. Apply `$result->headers` before serving content so crawlers get the correct licensing signals. **Pass `RequestContext` in frameworks.** Relying on `$_SERVER` directly works for plain PHP, but frameworks often normalize request data before it reaches `$_SERVER`. Use `RequestContext` to ensure the SDK reads the right values. **Debug mode logs to `error_log()`.** Enable with `debug: true` to trace token fetching, license XML parsing, and URL matching. ## API Reference ### Static Methods | Method | Description | | -------------------------------------------------------------- | ------------------------------------------------- | | `verify(token, resourceUrl, ...)` | Verify a token without analytics recording | | `obtainLicenseToken(clientId, clientSecret, resourceUrl, ...)` | Acquire a license token as a crawler client | | `fetchLicenseXml(merchantSystemUrn, ...)` | Fetch RSL license XML from Supertab Connect | | `resetInstance()` | Clear the singleton, allowing fresh instantiation | ### Instance Methods | Method | Description | | ------------------------------------------------ | ----------------------------------------------------------------------------- | | `handleRequest(?RequestContext $context)` | Handle a request end-to-end — detection, verification, enforcement, analytics | | `verifyAndRecord(token, resourceUrl, userAgent)` | Verify a token and record a usage event | # Python SDK Source: https://connect-docs.supertab.co/reference/sdk/python The Supertab Connect Python SDK lets publishers implement Really Simple Licensing (RSL) and the Crawler Authentication Protocol (CAP) in Python applications. The SDK handles license token verification, bot detection, enforcement decisions, opt-in analytics for agent & bot classification, and customer-side license token acquisition. **Requirements:** Python 3.12+. ## Installation Install the SDK from PyPI: ```bash theme={null} pip install supertab-connect-sdk ``` ## Initializing the Client The merchant client is async and uses `httpx.Request` for request handling. ```python theme={null} from supertab_connect import ( EnforcementMode, SupertabConnect, SupertabConnectConfig, default_bot_detector, ) client = SupertabConnect( SupertabConnectConfig( api_key="stc_live_your_api_key", # read from environment variables or secrets management enforcement=EnforcementMode.OBSERVE, analytics_enabled=True, # emit events for bot classification (off by default) bot_detector=default_bot_detector, debug=False, ) ) ``` ### Configuration Options | Property | Type | Required | Default | Description | | ------------------- | --------------------- | -------- | --------- | ------------------------------------------------------------------------- | | `api_key` | `str` | Yes | — | Your Supertab Merchant API Key | | `enforcement` | `EnforcementMode` | No | `OBSERVE` | How to handle bots without a valid token | | `analytics_enabled` | `bool` | No | `False` | Emit per-request events for agent & bot classification | | `bot_detector` | `BotDetector \| None` | No | `None` | Function that receives an `httpx.Request` and returns whether it is a bot | | `debug` | `bool` | No | `False` | Enables verbose SDK logging through Python logging | The SDK enforces a singleton pattern per API key. Creating another client with the same key returns the existing instance. Creating one with a different key raises an error unless you call `SupertabConnect.reset_instance()` or pass `reset=True`. ## Common Workflows ### Handle a Protected Request Use `handle_request()` when you want the SDK to manage the full lifecycle: 1. Extract a token from the `Authorization: License ` header. 2. Verify the token against the Supertab JWKS. 3. Record a license-usage event, plus an analytics event when `analytics_enabled` is set. 4. If no token is present, run bot detection and apply the enforcement mode. ```python theme={null} import httpx from supertab_connect import ( EnforcementMode, HandlerAction, SupertabConnect, SupertabConnectConfig, default_bot_detector, ) client = SupertabConnect( SupertabConnectConfig( api_key="stc_live_your_api_key", enforcement=EnforcementMode.ENFORCE, bot_detector=default_bot_detector, ) ) request = httpx.Request( "GET", "https://example.com/premium/article", headers={ "Authorization": "License your.jwt.token", "User-Agent": "Mozilla/5.0", "Accept": "text/html", "Accept-Language": "en-US", "Sec-CH-UA": '"Chromium";v="123"', }, ) # Inside an async function async with client: result = await client.handle_request(request) if result["action"] is HandlerAction.BLOCK: status = result["status"] headers = result["headers"] body = result["body"] # Return this response from your framework else: headers = result.get("headers", {}) # Apply returned headers, then serve content ``` ### Framework Integration Create an `httpx.Request` from your framework request object, then translate the `HandlerResult` back to a framework response. For example, in FastAPI: ```python theme={null} from contextlib import asynccontextmanager from fastapi import FastAPI, Request, Response import httpx from supertab_connect import ( EnforcementMode, HandlerAction, SupertabConnect, SupertabConnectConfig, default_bot_detector, ) connect = SupertabConnect( SupertabConnectConfig( api_key="stc_live_your_api_key", enforcement=EnforcementMode.OBSERVE, bot_detector=default_bot_detector, ) ) @asynccontextmanager async def lifespan(_app: FastAPI): try: yield finally: await connect.aclose() app = FastAPI(lifespan=lifespan) @app.get("/premium/article") async def premium_article(request: Request): sdk_request = httpx.Request( request.method, str(request.url), headers=dict(request.headers), ) result = await connect.handle_request(sdk_request) if result["action"] is HandlerAction.BLOCK: return Response( content=result["body"], status_code=result["status"], headers=result["headers"], ) return Response( content="Premium content", headers=result.get("headers", {}), ) ``` ### Verify a Token and Record Usage Use `verify_and_record()` when you need custom routing or response handling but still want usage and billing events recorded. ```python theme={null} from supertab_connect import EnforcementMode, SupertabConnect, SupertabConnectConfig client = SupertabConnect( SupertabConnectConfig( api_key="stc_live_your_api_key", enforcement=EnforcementMode.OBSERVE, ) ) # Inside an async function async with client: result = await client.verify_and_record( token="your.jwt.token", resource_url="https://example.com/premium/article", user_agent="Mozilla/5.0", request_headers={ "Accept": "text/html", "Accept-Language": "en-US", }, ) if not result.valid: # Return 401 or another response appropriate for your application print(result.error) ``` ### Verify Without Recording Use the static `verify()` method when you only need to check token validity and do not want analytics side effects. ```python theme={null} from supertab_connect import SupertabConnect # Inside an async function result = await SupertabConnect.verify( token="your.jwt.token", resource_url="https://example.com/premium/article", ) if not result.valid: print(result.error) ``` ### Obtaining a License Token Use `obtain_license_token()` when you are building a crawler or client that needs to access protected resources. The SDK fetches the publisher's `license.xml`, finds the best matching content rule, exchanges your client credentials for a token, and caches tokens in memory until shortly before expiry. ```python theme={null} from supertab_connect import obtain_license_token # Inside an async function token = await obtain_license_token( client_id="your_client_id", client_secret="your_client_secret", resource_url="https://example.com/premium/article", ) if token is not None: headers = {"Authorization": f"License {token}"} # Use these headers on the resource request ``` If you pass a `usage` value and the matching RSL content permits that usage without a token server, the function returns `None` because no token is required. ```python theme={null} from supertab_connect import UsageType, obtain_license_token # Inside an async function token = await obtain_license_token( client_id="your_client_id", client_secret="your_client_secret", resource_url="https://example.com/public-resource", usage=UsageType.AI_INPUT, ) ``` ## Important Types ### `EnforcementMode` Enforcement modes determine what happens to a **bot** request. Non-bot traffic is always allowed. * `DISABLED`: No verification — every request passes through untouched. * `OBSERVE` (Default): Tokens are verified and outcomes recorded. A bot with **no** token is allowed through with RSL signaling headers (`Link`, `X-RSL-Status`) indicating a license is required; a bot presenting an **invalid** token is still blocked. * `ENFORCE`: Blocks any bot without a valid token — `401 Unauthorized` (missing or invalid token) or `403 Forbidden` (token valid but wrong audience). Invalid tokens are always blocked except in `DISABLED` mode. ### `HandlerResult` Returned by `handle_request()`: * `{ "action": HandlerAction.ALLOW, "headers": ... }`: The request should proceed. Apply returned headers if present. * `{ "action": HandlerAction.BLOCK, "status": ..., "body": ..., "headers": ... }`: The request should be rejected with the provided response data. ### `RSLVerificationResult` Returned by `verify()` and `verify_and_record()`: | Field | Type | Description | | ------- | ------------- | ----------------------------------------------------- | | `valid` | `bool` | Whether the token is valid for the requested resource | | `error` | `str \| None` | Error message when invalid | ## Error Handling The high-level helpers return framework-friendly result shapes rather than typed invalid-token reason codes: * `handle_request()` returns a `HandlerResult` with `action`, and when blocked, response `status`, `body`, and `headers`. * `verify()` and `verify_and_record()` return `RSLVerificationResult(valid=False, error=...)`. Treat `error` as a message for logs or responses. If your application needs to branch on a machine-readable reason, call the lower-level `verify_license_token()` function and inspect `InvalidLicenseToken.reason`. Common invalid-token reasons include: * `missing_license_token`: No license token was provided. * `invalid_license_header`: The JWT header is malformed. * `invalid_license_algorithm`: The token uses an unsupported signing algorithm. * `invalid_license_payload`: The JWT payload is malformed. * `invalid_license_issuer`: The token issuer is not recognized. * `license_signature_verification_failed`: The token signature could not be verified. * `license_token_expired`: The token has expired. * `invalid_license_audience`: The token is valid but does not cover the requested URL. * `server_error`: The SDK could not validate the token because of a platform-side or JWKS fetch error. ## Tips and Pitfalls **Pass a bot detector for enforcement.** By default, `bot_detector` is `None`, so requests without tokens are treated as non-bot traffic. Use `default_bot_detector` or provide your own detector if you expect `handle_request()` to signal or block missing-token bots. **Apply returned headers.** In `OBSERVE` mode, the SDK signals licensing requirements through response headers on allowed requests. If you drop those headers, crawlers will not receive the correct RSL signal. **Use the async context manager.** `async with client:` closes shared HTTP clients used for event recording and JWKS fetching. For long-lived web apps, create one client at startup and close it during shutdown. **Cache behavior is in-memory.** `obtain_license_token()` caches `license.xml` by origin and license tokens by client, token server, and matched URL pattern. Process restarts clear that cache. ## API Reference ### Static and Module Functions | Method | Description | | ---------------------------------------------------------------------- | --------------------------------------------------------------- | | `SupertabConnect.verify(*, token, resource_url, ...)` | Verify a token without analytics recording | | `obtain_license_token(*, client_id, client_secret, resource_url, ...)` | Acquire a license token as a crawler client | | `verify_license_token(token, request_url, supertab_base_url, ...)` | Lower-level token verification with typed valid/invalid results | | `SupertabConnect.reset_instance()` | Clear the singleton, allowing fresh client initialization | | `SupertabConnect.set_base_url(url)` | Override the default Supertab Connect API base URL | ### Instance Methods | Method | Description | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | `handle_request(request)` | Handle a request end-to-end: token extraction, verification, bot detection, enforcement, and analytics | | `verify_and_record(*, token, resource_url, user_agent, ...)` | Verify a token and record a usage event | | `aclose()` | Close SDK-managed async HTTP clients | # TypeScript SDK Source: https://connect-docs.supertab.co/reference/sdk/typescript The Supertab Connect TypeScript SDK allows publishers to implement Really Simple Licensing (RSL) and the Crawler Authentication Protocol (CAP) directly in their applications or at the CDN edge. The SDK manages license token verification, bot detection, and licensing event recording with minimal configuration. ## Overview Supertab Connect helps you manage how bots and automated systems access your content. It uses license tokens (JWTs) to verify that a caller has a valid license to access a specific resource. ### Key Features * **Edge-Ready**: Optimized for Cloudflare Workers, Fastly Compute, and AWS CloudFront Lambda\@Edge. * **Flexible Enforcement**: Observe and signal, or strictly block, unlicensed requests. * **Plugin Bot Detection**: Built-in logic to identify common AI crawlers and headless browsers, customizable using signals from your WAF provider. * **Analytics**: Opt in with `analyticsEnabled` to emit per-request events for agent & bot classification. ## Installation Install the SDK using your preferred package manager: ```bash theme={null} npm install @getsupertab/supertab-connect-sdk ``` ## Quickstart: Fastly Compute The fastest way to get started is using one of the built-in CDN handlers. For Fastly Compute, read your API key from the Secret Store and pass your origin backend name. ```typescript theme={null} /// import { SupertabConnect } from "@getsupertab/supertab-connect-sdk"; import { SecretStore } from "fastly:secret-store"; const secrets = new SecretStore("supertab_config"); const merchantApiKey = (await secrets.get("MERCHANT_API_KEY")).plaintext(); addEventListener("fetch", (event) => { event.respondWith( SupertabConnect.fastlyHandleRequests( event, merchantApiKey, "origin_backend_name", { enableRSL: true, // Optionally host /license.xml merchantSystemUrn: "your_website_urn", analyticsEnabled: true, // emit events for bot classification } ) ); }); ``` ## Initializing the Client If you aren't using a convenience handler, you can initialize the `SupertabConnect` client manually. The client follows a singleton pattern. ```typescript theme={null} import { SupertabConnect, EnforcementMode } from "@getsupertab/supertab-connect-sdk"; const supertab = new SupertabConnect({ apiKey: "stc_live_...", // API Keys should be read from environment variables or secrets management enforcement: EnforcementMode.OBSERVE, // Defaults to OBSERVE if not provided analyticsEnabled: true, // emit events for bot classification (off by default) debug: false // When enabled debug mode prints more logging information about token handling }); ``` ### Configuration Options | Property | Type | Description | | :----------------- | :---------------- | :-------------------------------------------------------------------------------------------------------------- | | `apiKey` | `string` | **Required.** Your Supertab Merchant API Key. | | `enforcement` | `EnforcementMode` | Controls how unlicensed requests are handled. Defaults to `OBSERVE`. See [`EnforcementMode`](#enforcementmode). | | `analyticsEnabled` | `boolean` | Emit per-request events for agent & bot classification. Defaults to `false`. | | `botDetector` | `BotDetector` | A custom function to identify bots. Defaults to `defaultBotDetector`. | | `debug` | `boolean` | Enables verbose logging to the console. | ## Common Workflows ### Edge Integration (CDN Handlers) The SDK provides static methods that handle the entire request/response lifecycle for specific platforms. These handlers: 1. Extract tokens from the `Authorization: License ` header. 2. Verify the token against the Supertab JWKS. 3. Record a license-usage event, plus an analytics event when `analyticsEnabled` is set. 4. If no token is present, run bot detection and apply the enforcement mode. #### Fastly Compute ```typescript theme={null} import { SupertabConnect } from "@getsupertab/supertab-connect-sdk"; addEventListener("fetch", (event) => { event.respondWith( SupertabConnect.fastlyHandleRequests( event, "YOUR_MERCHANT_API_KEY", "your-origin-backend-name", { enableRSL: true, // Automatically hosts /license.xml merchantSystemUrn: "urn:stc:merchant:system:..." } ) ); }); ``` #### Cloudflare Workers Always pass `ctx` — the SDK uses its `waitUntil` to send events (license-usage, plus analytics when enabled) in the background without blocking the response. The API key is read from the `env` object (`MERCHANT_API_KEY` secret). ```typescript theme={null} import { SupertabConnect, Env } from "@getsupertab/supertab-connect-sdk"; export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { return SupertabConnect.cloudflareHandleRequests(request, env, ctx, { analyticsEnabled: true, }); }, }; ``` #### AWS CloudFront (Lambda\@Edge) Note: This handler is designed for **Origin Request** events. ```typescript theme={null} import { SupertabConnect, CloudFrontRequestEvent } from "@getsupertab/supertab-connect-sdk"; export async function handler(event: CloudFrontRequestEvent) { return SupertabConnect.cloudfrontHandleRequests(event, { apiKey: "YOUR_MERCHANT_API_KEY" }); } ``` ### Manual Verification Use `verifyAndRecord` when you need granular control or are running in a standard Node.js/Bun/Deno backend. ```typescript theme={null} const result = await supertab.verifyAndRecord({ token: "...", // Extracted from header or other source resourceUrl: "https://example.com/premium-article", userAgent: request.headers.get("User-Agent"), ctx: ctx // Optional: Pass context to use waitUntil for non-blocking analytics }); if (result.valid) { // Allow access to content } else { // Handle invalid license (e.g., return 401) console.error(result.error); } ``` ### Obtaining a License Token If you are building a client that needs to access protected resources, use `obtainLicenseToken` to get a license token. The SDK handles retrieval of the licensing details and automatically refreshes the token when needed. Whenever a usage type is specified and a token is not required (the matched content rule permits the intended usage without a license), the method returns no token (`undefined`). You should call `obtainLicenseToken` before every request, the SDK will handle caching and expiration. ```typescript theme={null} const token = await SupertabConnect.obtainLicenseToken({ clientId: "your_client_id", clientSecret: "your_client_secret", resourceUrl: "https://example.com/protected-resource" }); if (token) { const headers = { Authorization: `License ${token}` }; // Use these headers on the resource request. } ``` When you know the intended content usage type, pass `usage`. If there is a matching `` rule with the license explicitly permitting that usage without requiring a license token, the SDK returns `undefined`. This allows you to treat `undefined` as "no token needed" rather than an error. ```typescript theme={null} import { SupertabConnect, UsageType } from "@getsupertab/supertab-connect-sdk"; const token = await SupertabConnect.obtainLicenseToken({ clientId: "your_client_id", clientSecret: "your_client_secret", resourceUrl: "https://example.com/public-resource", usage: UsageType.SEARCH }); if (token) { const headers = { Authorization: `License ${token}` }; // Use these headers on the resource request. } ``` ## Important Types ### `EnforcementMode` Enforcement modes determine what happens to a **bot** request. Non-bot traffic is always allowed. * `DISABLED`: No verification — every request passes through untouched. * `OBSERVE` (Default): Tokens are verified and outcomes recorded. A bot with **no** token is allowed through with RSL signaling headers (`Link`, `X-RSL-Status`) indicating a license is required; a bot presenting an **invalid** token is still blocked. * `ENFORCE`: Blocks any bot without a valid token — `401 Unauthorized` (missing or invalid token) or `403 Forbidden` (token valid but wrong audience). Invalid tokens are always blocked except in `DISABLED` mode. ### Handler Result When calling `handleRequest` manually, you receive a `HandlerResult`: * `{ action: "allow", headers?: ... }`: The request should proceed. * `{ action: "block", status: number, body: string, headers: ... }`: The request should be rejected with the provided response. ## Error Handling The SDK provides clear error reasons when a license is invalid. Common reasons include: * `missing_license_token`: No license was provided in the headers. * `license_token_expired`: The JWT `exp` claim is in the past. * `invalid_license_audience`: The token is valid but not for the requested URL. * `license_signature_verification_failed`: The token was tampered with or signed by an untrusted issuer. ## Tips and Pitfalls * **Performance**: When using Cloudflare Workers, always pass the `ExecutionContext` (`ctx`) to the handlers. This lets the SDK send events (license-usage, plus analytics when enabled) in the background without delaying the response to the user. * **Singleton Pattern**: The `SupertabConnect` constructor returns the existing instance if one was already created with the same API key. Use `SupertabConnect.resetInstance()` if you need to change configurations dynamically. * **Custom Bot Detection**: If you have specific traffic patterns (e.g., a known internal scraper), provide a custom `botDetector` function to prevent false positives. * **No token required**: `obtainLicenseToken` returning `undefined` is valid when `usage` matches content without server URL that permits that usage. Treat it as "no token needed", not as an authentication failure. * **Cache behavior**: `obtainLicenseToken` caches `license.xml` by origin for 15 minutes and license tokens by client, token server, and matched URL pattern. Process restarts clear that cache. ```typescript theme={null} import { defaultBotDetector } from "@getsupertab/supertab-connect-sdk"; const customDetector = (request: Request) => { const ua = request.headers.get("User-Agent"); return ua?.includes("MyInternalBot") ? false : defaultBotDetector(request); }; ``` ## API Reference ### Static Methods * `cloudflareHandleRequests(request, env, ctx, options?)`: Cloudflare-specific handler. * `fastlyHandleRequests(event, apiKey, backend, options?)`: Fastly-specific handler. Takes the Fastly `FetchEvent`, not `event.request`. * `cloudfrontHandleRequests(event, options)`: CloudFront-specific handler. * `verify(options)`: Pure token verification (no event recording). * `obtainLicenseToken(options)`: Client-side token acquisition. ### Instance Methods * `handleRequest(request, context?)`: The core logic used by CDN handlers. `context` is a `HandleRequestContext` object (`{ ctx?, sourceCdn?, clientIp?, ... }`), not a bare execution context. * `verifyAndRecord(options)`: Verifies a token and records the usage event. Returns `{ valid, error? }`.