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

# Host & A/B Test Any Page

> Deploy any HTML page or SPA onto TagadaPay and run geo or weighted A/B tests — no SDK required

# Host & A/B Test Any Page

<Note>
  **Advanced guide.** This walks through raw REST API calls. For most use cases, the [CLI](/developer-tools/cli/introduction) or [dashboard](https://app.tagadapay.com) is simpler. See the [Developer Tools overview](/developer-tools/index) if you're not sure this is what you need.
</Note>

TagadaPay can host **any static HTML page or single-page application** and serve it on a fast global CDN (\~10ms TTFB). You can then split traffic between variants using weighted or geo-based A/B testing — all through the REST API.

***

## Why Host on TagadaPay Instead of Your Own Servers?

| Benefit                                   | Details                                                                                                                                                                                                                   |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Edge-fast serving**                     | Pages are served from a global edge network (\~10 ms TTFB). A/B variant selection happens at the edge too — no extra round-trip to an origin server, so visitors never feel the split.                                    |
| **Same-URL A/B testing**                  | Both variants live on the **exact same URL**. The routing engine picks the right one transparently. This is critical for ad campaigns, email links, and SEO — you don't need separate URLs or client-side redirect hacks. |
| **Custom domains & routing in one click** | Bring your own domain, point a CNAME, verify via API, done. TagadaPay handles TLS, routing, and CDN caching automatically. No nginx configs, no Cloudflare rules, no load balancers.                                      |
| **Free hosting for TagadaPay users**      | Hosting is included at no extra cost when you use TagadaPay. No bandwidth bills, no storage fees, no infra to maintain.                                                                                                   |

### Use Cases

| Use case                 | Example                                                     |
| ------------------------ | ----------------------------------------------------------- |
| **Landing page testing** | Test two headline variants to see which converts better     |
| **Geo-targeted content** | Show a French page to EU visitors, English to US visitors   |
| **Gradual rollouts**     | Send 10% of traffic to a new design before going 100%       |
| **External SPAs**        | Host a React/Vue/Svelte app without managing your own infra |

Every page you deploy gets:

* A unique URL on `*.cdn.tagadapay.com` (or your own domain)
* Instant global delivery via edge CDN
* Built-in A/B split testing (weighted or geo) on the same URL
* Sticky sessions so returning visitors see the same variant

***

## Prerequisites

<AccordionGroup>
  <Accordion title="TagadaPay account" icon="user">
    Sign up at [app.tagadapay.com](https://app.tagadapay.com) and create a **store**.
  </Accordion>

  <Accordion title="API key" icon="key">
    Go to **Settings → API Keys** in your dashboard and generate one. It looks like `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
  </Accordion>

  <Accordion title="curl (or any HTTP client)" icon="terminal">
    All examples use `curl`. You can use Postman, httpie, or any language's HTTP library.
  </Accordion>
</AccordionGroup>

***

## The 3-Step Flow

Every deployment follows the same pattern:

```
Deploy → Instantiate → Mount
```

| Step            | What it does                                               |
| --------------- | ---------------------------------------------------------- |
| **Deploy**      | Uploads your files (HTML, CSS, JS, images) to blob storage |
| **Instantiate** | Creates a running instance of that deployment for a store  |
| **Mount**       | Binds the instance to a URL so visitors can access it      |

After that, you can optionally **configure A/B split testing** on the mounted route.

***

## Step 1 — Deploy a Page

Create a simple HTML file and deploy it via the API. Assets are sent as **base64-encoded** strings.

<Steps>
  <Step title="Prepare your HTML">
    Any valid HTML works. Here's a minimal example:

    ```html theme={null}
    <!DOCTYPE html>
    <html>
    <head><title>My Landing Page</title></head>
    <body>
      <h1>Welcome!</h1>
      <p>This page is hosted on TagadaPay.</p>
    </body>
    </html>
    ```
  </Step>

  <Step title="Base64-encode the file">
    ```bash theme={null}
    base64 -i index.html
    # Copy the output — you'll paste it in the next step
    ```
  </Step>

  <Step title="Call the Deploy API">
    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/deploy \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "storeId": "YOUR_STORE_ID",
        "manifest": {
          "name": "my-landing-page",
          "version": "1.0.0",
          "pages": [
            { "id": "main", "path": "/", "name": "Landing Page" }
          ]
        },
        "inlineAssets": [
          {
            "path": "/index.html",
            "content": "PASTE_BASE64_HERE",
            "contentType": "text/html"
          }
        ]
      }'
    ```

    Save the `pluginId` and `deploymentId` from the response — you'll need them next.
  </Step>
</Steps>

<Tip>
  You can deploy **multiple files** (CSS, JS, images) by adding more entries to `inlineAssets`. Each file needs a `path`, base64 `content`, and `contentType`.
</Tip>

***

## Step 2 — Instantiate

An instance is a "live copy" of a deployment, bound to a specific store. You can create multiple instances of the same deployment with different configurations.

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/instantiate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pluginId": "PLUGIN_ID_FROM_STEP_1",
    "deploymentId": "DEPLOYMENT_ID_FROM_STEP_1",
    "storeId": "YOUR_STORE_ID",
    "config": {}
  }'
```

Save the `instanceId` from the response.

### Instance Config

The `config` object is optional but powerful. Whatever you pass here is **injected into the served HTML** as `window.__TAGADA_PLUGIN_CONFIG__`, making it available to your JavaScript at runtime. This lets you deploy **one set of files** and customize behavior per-instance without redeploying.

```bash theme={null}
# Same deployment, different config per store/use case
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/instantiate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pluginId": "PLUGIN_ID",
    "deploymentId": "DEPLOYMENT_ID",
    "storeId": "YOUR_STORE_ID",
    "config": {
      "theme": "dark",
      "headline": "Spring Sale — 30% Off!",
      "ctaUrl": "https://yourstore.com/checkout",
      "showTimer": true,
      "timerEnd": "2026-04-01T00:00:00Z"
    }
  }'
```

Then read it in your page:

```html theme={null}
<script>
  var cfg = window.__TAGADA_PLUGIN_CONFIG__ || {};
  document.querySelector('h1').textContent = cfg.headline || 'Welcome';
  if (cfg.showTimer) startCountdown(cfg.timerEnd);
</script>
```

<Info>
  **You don't have to read `window.__TAGADA_PLUGIN_CONFIG__` manually.** The [TagadaPay Plugin SDK](/developer-tools/sdk/introduction) reads it for you and exposes it via `tgd.config`. Using the SDK is the recommended approach — it also gives you session management, analytics, and funnel navigation out of the box. Reading the global directly is perfectly valid if you want zero dependencies, but for most use cases the SDK is simpler.
</Info>

<Info>
  Additional globals like `window.__TAGADA_STORE_ID__` and `window.__TAGADA_ACCOUNT_ID__` (and matching `x-plugin-*` meta tags) are always injected regardless of the config you pass.
</Info>

**Common use cases:**

| Config field         | Purpose                                        |
| -------------------- | ---------------------------------------------- |
| `theme`, `colors`    | Brand customization without redeploying assets |
| `headline`, `cta`    | Copy testing across multiple instances         |
| `locale`, `currency` | Localization per region (combine with geo A/B) |
| `featureFlags`       | Gradual feature rollout                        |
| `analyticsId`        | Per-store tracking IDs                         |

***

## Step 3 — Mount

Mounting binds your instance to a hostname so it becomes reachable by visitors.

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/mount \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instanceId": "INSTANCE_ID_FROM_STEP_2",
    "storeId": "YOUR_STORE_ID",
    "hostname": "my-landing-page--YOUR_STORE_ID.cdn.tagadapay.com",
    "basePath": "/",
    "matcher": "/"
  }'
```

Save the `routeId` from the response — you'll need it if you set up A/B testing.

Your page is now live at:

```
https://my-landing-page--YOUR_STORE_ID.cdn.tagadapay.com/
```

***

## Set Up A/B Testing

You can split traffic on any mounted route. TagadaPay supports two split types:

| Type         | How it works                                 |
| ------------ | -------------------------------------------- |
| **weighted** | Split by percentage (e.g. 50/50, 90/10)      |
| **geo**      | Split by visitor country (ISO country codes) |

### Create a Second Variant

A variant is just another **instance**. There are two ways to create one:

<Tabs>
  <Tab title="Different deployment (different files)">
    Deploy entirely different HTML/CSS/JS (Step 1), then instantiate it (Step 2). Each variant has its own build assets — useful when the pages are structurally different (e.g. a completely redesigned layout vs. the original).

    You'll get a separate `pluginId`, `deploymentId`, and `instanceId` for the variant.
  </Tab>

  <Tab title="Same deployment, different config">
    Reuse the **same deployment** but create a second instance with a different `config`. The assets are identical — only the runtime configuration changes. This is faster and lighter: no need to re-upload files.

    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/instantiate \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "pluginId": "SAME_PLUGIN_ID",
        "deploymentId": "SAME_DEPLOYMENT_ID",
        "storeId": "YOUR_STORE_ID",
        "config": {
          "headline": "Free Shipping Today!",
          "theme": "light"
        }
      }'
    ```

    Same HTML, but `window.__TAGADA_PLUGIN_CONFIG__` will contain the new values — so the page renders differently at runtime.
  </Tab>
</Tabs>

<Info>
  You do **not** mount the second variant separately. Both variants share the **same URL** — the split testing system decides which one to serve.
</Info>

### Configure the Split

Call the split endpoint with the `routeId` from Step 3 and both instance IDs:

<Tabs>
  <Tab title="Geo-based">
    Route visitors to different variants based on their country.

    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/split \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "routeId": "ROUTE_ID_FROM_STEP_3",
        "storeId": "YOUR_STORE_ID",
        "splitConfig": {
          "type": "geo",
          "instances": [
            {
              "instanceId": "INSTANCE_A",
              "deploymentId": "DEPLOYMENT_A",
              "pluginId": "PLUGIN_A",
              "regions": ["US", "CA", "MX", "BR"]
            },
            {
              "instanceId": "INSTANCE_B",
              "deploymentId": "DEPLOYMENT_B",
              "pluginId": "PLUGIN_B",
              "regions": ["FR", "DE", "GB", "ES", "IT"]
            }
          ],
          "stickyDuration": 604800
        }
      }'
    ```

    Visitors from unlisted countries fall back to the first instance.
  </Tab>

  <Tab title="Weighted (percentage)">
    Split traffic by percentage across variants.

    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/split \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "routeId": "ROUTE_ID_FROM_STEP_3",
        "storeId": "YOUR_STORE_ID",
        "splitConfig": {
          "type": "weighted",
          "instances": [
            {
              "instanceId": "INSTANCE_A",
              "deploymentId": "DEPLOYMENT_A",
              "pluginId": "PLUGIN_A",
              "weight": 50
            },
            {
              "instanceId": "INSTANCE_B",
              "deploymentId": "DEPLOYMENT_B",
              "pluginId": "PLUGIN_B",
              "weight": 50
            }
          ],
          "stickyDuration": 604800
        }
      }'
    ```

    Weights must sum to **100**. Adjust as needed (e.g. `90`/`10` for a gradual rollout).
  </Tab>
</Tabs>

***

## How Sticky Sessions Work

When a visitor hits a split route for the first time, TagadaPay picks a variant and stores the choice in a **sticky session** cookie (`tgd-session-id`). On subsequent visits within `stickyDuration` seconds (default: 7 days), the same variant is served.

This ensures a consistent experience — a visitor won't flip between variants mid-session.

***

## Update a Variant

To push a new version of a variant, deploy with `"overwrite": true`:

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/deploy \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "YOUR_STORE_ID",
    "manifest": {
      "name": "my-landing-page",
      "version": "1.1.0",
      "pages": [
        { "id": "main", "path": "/", "name": "Landing Page" }
      ]
    },
    "inlineAssets": [
      {
        "path": "/index.html",
        "content": "NEW_BASE64_CONTENT",
        "contentType": "text/html"
      }
    ],
    "overwrite": true
  }'
```

The update propagates automatically to all routes using this deployment. No re-mount needed.

***

## Deploying a Full SPA

TagadaPay can host any single-page application (React, Vue, Svelte, etc.). Build your app, then deploy all output files:

```bash theme={null}
# Build your app
npm run build

# Encode each file and include in inlineAssets
# Example for a Vite React app (dist/ folder):
```

```json theme={null}
{
  "storeId": "YOUR_STORE_ID",
  "manifest": {
    "name": "my-react-app",
    "version": "1.0.0",
    "pages": [
      { "id": "main", "path": "/", "name": "App" }
    ]
  },
  "inlineAssets": [
    { "path": "/index.html", "content": "...", "contentType": "text/html" },
    { "path": "/assets/index-abc123.js", "content": "...", "contentType": "application/javascript" },
    { "path": "/assets/index-def456.css", "content": "...", "contentType": "text/css" }
  ]
}
```

<Tip>
  All non-HTML paths (JS, CSS, images, fonts) are served with long-lived cache headers. HTML is served with `no-cache` for instant updates.
</Tip>

***

## Advanced: Large File Uploads (> 4 MB)

<Info>
  The `inlineAssets` method shown above sends base64-encoded files inside the JSON body. This works well for small pages, but **serverless functions have a \~4.5 MB body limit**. If your SPA build output is larger — bundled JS, images, fonts — you need the **two-step blob upload** flow instead.
</Info>

<AccordionGroup>
  <Accordion title="How the blob upload flow works" icon="cloud-arrow-up">
    Instead of embedding assets in the request body, you:

    1. **Upload a ZIP** of your build output to TagadaPay's blob storage, which supports **multipart uploads up to 50 MB**.
    2. **Deploy from the blob URL** — pass the returned URL to the deploy endpoint, which downloads and extracts the ZIP server-side.

    This is the same flow the [TagadaPay CLI](/developer-tools/cli/introduction) and Studio use under the hood. The CLI automatically switches to blob upload when a build exceeds 4 MB.
  </Accordion>

  <Accordion title="Step-by-step with curl" icon="terminal">
    <Steps>
      <Step title="ZIP your build output">
        ```bash theme={null}
        cd dist/          # or build/, out/, etc.
        zip -r ../my-app.zip .
        cd ..
        ```
      </Step>

      <Step title="Get an upload token">
        The upload token endpoint implements a client upload protocol. You need to call it twice: once to get a token, then once more to confirm completion. In practice, use the `@vercel/blob` client library — it handles both calls for you.

        ```bash theme={null}
        npm install @vercel/blob
        ```

        ```javascript theme={null}
        import { upload } from '@vercel/blob/client';
        import { readFileSync } from 'fs';

        const zipBuffer = readFileSync('my-app.zip');

        const blob = await upload('my-app.zip', zipBuffer, {
          access: 'public',
          handleUploadUrl: 'https://app.tagadapay.com/api/public/v1/plugins/generate-upload-token',
          multipart: true,
          contentType: 'application/zip',
          clientPayload: JSON.stringify({ apiKey: 'YOUR_API_KEY' }),
        });

        console.log('Uploaded to:', blob.url);
        ```
      </Step>

      <Step title="Deploy from the blob URL">
        Now pass the blob URL to the V2 deploy endpoint using the `zipUrl` parameter instead of `inlineAssets`:

        ```bash theme={null}
        curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/deploy \
          -H "Authorization: Bearer YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "storeId": "YOUR_STORE_ID",
            "manifest": {
              "name": "my-react-app",
              "version": "1.0.0",
              "pages": [
                { "id": "main", "path": "/", "name": "App" }
              ]
            },
            "zipUrl": "BLOB_URL_FROM_PREVIOUS_STEP"
          }'
        ```

        The server downloads the ZIP, extracts all files, and uploads each one to blob storage — exactly like `inlineAssets`, but without the body size limit.
      </Step>

      <Step title="Continue as usual">
        From here, the flow is identical: **instantiate** → **mount** → optionally **configure A/B testing**. The `pluginId` and `deploymentId` from the response are the same as with inline assets.
      </Step>
    </Steps>
  </Accordion>

  <Accordion title="When to use which method" icon="scale-balanced">
    | Method                          | Max size             | Best for                                              |
    | ------------------------------- | -------------------- | ----------------------------------------------------- |
    | `inlineAssets` (base64 in JSON) | \~4 MB total payload | Single HTML pages, small sites, quick tests           |
    | `zipUrl` (blob upload)          | 50 MB                | Full SPAs, sites with images/fonts, production builds |

    <Tip>
      If you use the [TagadaPay CLI](/developer-tools/cli/introduction), you don't need to think about this — it detects the file size and picks the right method automatically.
    </Tip>
  </Accordion>
</AccordionGroup>

***

## Custom Domain

By default your page is served on `*.cdn.tagadapay.com`. You can mount it on your own domain instead using the Domains API.

<Steps>
  <Step title="Register the domain">
    Call the domains API to add your domain to TagadaPay. This registers it with the edge network and returns a verification token.

    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/domains/add \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "domain": "landing.yourstore.com"
      }'
    ```

    Response:

    ```json theme={null}
    {
      "success": true,
      "domain": {
        "id": "cdm_abc123",
        "domain": "landing.yourstore.com",
        "isVerified": false,
        "enabled": false,
        "verificationToken": "vrf_token_xyz"
      }
    }
    ```

    Save the `id` — you'll use it to verify.
  </Step>

  <Step title="Configure DNS">
    Add the required DNS records at your registrar. You need a **CNAME** pointing to `cname.vercel-dns.com`:

    | Type  | Name      | Value                  |
    | ----- | --------- | ---------------------- |
    | CNAME | `landing` | `cname.vercel-dns.com` |

    Wait for DNS propagation (usually a few minutes, can take up to 48h).
  </Step>

  <Step title="Verify the domain">
    Once DNS has propagated, call the verify endpoint:

    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/domains/verify \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "domainId": "cdm_abc123"
      }'
    ```

    A successful response looks like:

    ```json theme={null}
    {
      "status": "Valid Configuration",
      "ownershipStatus": "Verified"
    }
    ```
  </Step>

  <Step title="Mount on the custom domain">
    Once verified, mount your instance using the `customDomain` field:

    ```bash theme={null}
    curl -X POST https://app.tagadapay.com/api/public/v1/plugins/v2/mount \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "deploymentId": "YOUR_DEPLOYMENT_ID",
        "deploymentInstanceId": "YOUR_INSTANCE_ID",
        "storeId": "YOUR_STORE_ID",
        "customDomain": "landing.yourstore.com",
        "basePath": "/",
        "matcher": "/"
      }'
    ```

    Your page is now live at `https://landing.yourstore.com/`.
  </Step>
</Steps>

<Tip>
  HTTPS is provisioned automatically. A/B testing, sticky sessions, and all other features work identically on custom domains.
</Tip>

You can also list and remove domains:

```bash theme={null}
# List all your domains
curl -X POST https://app.tagadapay.com/api/public/v1/domains/list \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

# Remove a domain
curl -X POST https://app.tagadapay.com/api/public/v1/domains/remove \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domainId": "cdm_abc123" }'
```

***

## API Reference

### Write Operations

| Endpoint                                       | Method | Description                                                       |
| ---------------------------------------------- | ------ | ----------------------------------------------------------------- |
| `/api/public/v1/plugins/v2/deploy`             | POST   | Upload files and create a deployment (`inlineAssets` or `zipUrl`) |
| `/api/public/v1/plugins/generate-upload-token` | POST   | Get a blob upload token for large files (multipart, up to 50 MB)  |
| `/api/public/v1/plugins/v2/instantiate`        | POST   | Create a live instance from a deployment                          |
| `/api/public/v1/plugins/v2/mount`              | POST   | Bind an instance to a URL (hostname or custom domain)             |
| `/api/public/v1/plugins/v2/split`              | POST   | Configure A/B testing on a route                                  |
| `/api/public/v1/domains/add`                   | POST   | Register a custom domain                                          |
| `/api/public/v1/domains/verify`                | POST   | Verify DNS configuration                                          |
| `/api/public/v1/domains/remove`                | POST   | Remove a custom domain                                            |

### Read Operations

| Endpoint                                                  | Method | Description                                                            |
| --------------------------------------------------------- | ------ | ---------------------------------------------------------------------- |
| `/api/public/v1/plugins/v2/deployments/{deploymentId}`    | GET    | Get deployment details (version, pages) — no storeId required          |
| `/api/public/v1/plugins/v2/instances/{storeId}`           | GET    | List all instances for a store (optionally filter by `?deploymentId=`) |
| `/api/public/v1/plugins/v2/instances/detail/{instanceId}` | GET    | Get a single instance with its config and deployment data              |
| `/api/public/v1/plugins/v2/list/{storeId}`                | GET    | List all plugins for a store                                           |
| `/api/public/v1/plugins/v2/mount-points/{storeId}`        | GET    | List mount points for a store                                          |
| `/api/public/v1/plugins/v2/details/{pluginId}`            | GET    | Get plugin details                                                     |
| `/api/public/v1/domains/list`                             | POST   | List all custom domains                                                |

### Funnel Operations

| Endpoint                                    | Method | Description                                          |
| ------------------------------------------- | ------ | ---------------------------------------------------- |
| `/api/public/v1/funnels/{funnelId}`         | GET    | Get funnel config (nodes now include `deploymentId`) |
| `/api/public/v1/funnels/{funnelId}`         | PUT    | Update funnel with automatic routing                 |
| `/api/public/v1/funnels/{funnelId}/promote` | POST   | Promote staging to production (handles prebuilds)    |

All endpoints require `Authorization: Bearer YOUR_API_KEY` and `Content-Type: application/json`.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="404 after mounting" icon="circle-exclamation">
    Make sure the `hostname` in the mount request matches the URL you're visiting.
    Format: `{slug}--{storeId}.cdn.tagadapay.com`

    Double-check that `basePath` is `/` and `matcher` is `/`.
  </Accordion>

  <Accordion title="A/B test always shows the same variant" icon="circle-exclamation">
    Sticky sessions may be pinning you to one variant. Clear your cookies or use incognito mode to get a fresh session. For geo testing, the system reads the visitor's country from the edge network — make sure you're testing from the expected country or use a VPN.
  </Accordion>

  <Accordion title="Assets (CSS/JS) return 404" icon="circle-exclamation">
    Ensure each asset's `path` in `inlineAssets` matches the path referenced in your HTML.
    For example, if your HTML has `<script src="/assets/app.js">`, the asset path must be `/assets/app.js`.
  </Accordion>

  <Accordion title="Deploy fails with 'Plugin name is required'" icon="circle-exclamation">
    The `manifest` object requires at minimum: `name`, `version`, and `pages` (array with at least one entry). Each page needs `id`, `path` (starting with `/`), and `name`.
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Plugin SDK" icon="puzzle-piece" href="/developer-tools/sdk/introduction">
    Add session management, payment processing, and funnel navigation to your pages
  </Card>

  <Card title="Plugin CLI" icon="terminal" href="/developer-tools/cli/introduction">
    Automate deployments with the CLI instead of curl
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Explore all available API endpoints
  </Card>

  <Card title="Custom Domains" icon="globe" href="https://help.tagada.io/custom-domains">
    Use your own domain instead of the default CDN hostname
  </Card>
</CardGroup>
