Skip to content
clonesite.ai
Menu
10 min readguides

Clone a Website With an API — Built for Agents

A developer's guide to the Clonesite clone API. Create one key, then let an agent create clone requests, poll status, handle webhooks, and download editable React and Tailwind source — end to end.

Diagram of an agent discovering the Clonesite clone API from one key, llms.txt, and openapi.json.

Most APIs assume a human will read the docs, wire up the calls, and babysit the integration. This one doesn't.

With the Clonesite clone API, a person does exactly one thing — create a key — and an agent does everything else: it preflights the request, runs a free mock integration test, turns a live URL into editable React and Tailwind source, waits for the build, and downloads the code. You make the key; it does the rest.

End-to-end sequence: a human creates an API key, then the agent creates a clone request, polls or receives a webhook, and downloads the source ZIP.

One human step, then a loop the agent owns: check, create, wait, download.

The mental model

The integration has one human action and five API endpoints:

  • Human (once): create an API key on /developers.
  • Agent (before spending): POST /clone-requests/preflight and, during setup, POST /clone-requests/test-runs.
  • Agent (live clone): POST a clone request, poll the status (or receive a webhook), then POST to unlock and download the source.

That's it. No SDK is required, no browser session, no dashboard clicking after the key exists. The base URL is:

https://clonesite.ai/api/v1

Step 1 — Create a key (the only human step)

Sign in, open /developers, and click Create your first key. You'll see a value like cs_live_a1b2c3d4... exactly once. It's stored hashed, so copy it immediately and hand it to your agent (or drop it in an environment variable).

Treat the key like a password: anyone holding it can spend your credits. The developers page even gives you a ready-to-paste agent brief:

# Clone any site via the Clonesite API
base       https://clonesite.ai/api/v1
auth       x-api-key: cs_live_...
spec       https://clonesite.ai/openapi.json
discovery  https://clonesite.ai/llms.txt

Agents can't self-register or mint their own credentials. A human account owner always creates the key first — then an agent uses it.

Step 2 — Authenticate every call

Every request carries the key in the x-api-key header. There is no OAuth dance and no bearer token exchange:

curl https://clonesite.ai/api/v1/clone-requests/api_req_example \
  -H "x-api-key: cs_live_a1b2c3d4..."

Each key is scoped to explicit permissions: clone_requests:create, clone_requests:read, and source_downloads:create. Preflight and test-runs use the create permission, status polling uses read, and source ZIP downloads use the source-download permission.

Step 3 — Preflight and run a free test

Preflight validates a request without spending credits; test runs rehearse the whole loop for free.

Preflight checks before you charge. Test runs exercise polling, webhooks, and downloads for free.

Before spending credits, validate the same payload with preflight. It checks the key, permission, payload, credits, and webhook configuration, but does not write a request, create a job, charge credits, or send a webhook.

curl -X POST https://clonesite.ai/api/v1/clone-requests/preflight \
  -H "x-api-key: cs_live_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "sourceUrl": "https://stripe.com",
    "prompt": "Clone this site as an editable React and Tailwind app.",
    "externalRequestId": "order_123",
    "callbackUrl": "https://your-app.com/webhooks/clonesite"
  }'

A passing preflight confirms the call will work — without creating anything or moving credits:

{
  "ok": true,
  "mode": "preflight",
  "canCreate": true,
  "wouldChargeCredits": 5,
  "wouldCreateCloneRequest": false,
  "wouldCreateJob": false,
  "checks": {
    "apiKey": "valid",
    "permission": "clone_requests:create",
    "payload": "valid",
    "credits": "sufficient",
    "webhook": "configured"
  }
}

During setup, run test-runs with the same live key and webhook handler. It creates a free mode: "test" request and sends signed clone.ready or clone.failed webhooks. The response is the same request object a live call returns — not a download URL — so you exercise polling, webhooks, and even a free fixture source download through the exact same endpoints, without paying for a real clone.

curl -X POST https://clonesite.ai/api/v1/clone-requests/test-runs \
  -H "x-api-key: cs_live_a1b2c3d4..." \
  -H "Idempotency-Key: test_order_123" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceUrl": "https://stripe.com",
    "prompt": "Test my Clone API integration.",
    "externalRequestId": "test_order_123",
    "callbackUrl": "https://your-app.com/webhooks/clonesite",
    "scenario": "ready"
  }'

The test run returns mode: "test". Poll it or wait for the webhook exactly like a live request — it reaches ready (or failed, per scenario) without spending a credit:

{
  "id": "api_req_def456",
  "mode": "test",
  "status": "queued",
  "sourceZip": { "available": false, "unlocked": false, "creditCost": 0 },
  "statusUrl": "/api/v1/clone-requests/api_req_def456"
}

Step 4 — Create a live clone request

Send the public URL you want to clone and a prompt describing the output. The Idempotency-Key header is required on live create and test-runs — it's what makes retries safe.

curl -X POST https://clonesite.ai/api/v1/clone-requests \
  -H "x-api-key: cs_live_a1b2c3d4..." \
  -H "Idempotency-Key: order_123" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceUrl": "https://stripe.com",
    "prompt": "Clone this site as an editable React and Tailwind app.",
    "externalRequestId": "order_123",
    "callbackUrl": "https://your-app.com/webhooks/clonesite"
  }'

externalRequestId and callbackUrl are optional. You get back 202 Accepted and a stable api_req_* id:

{
  "id": "api_req_abc123",
  "mode": "live",
  "status": "queued",
  "sourceUrl": "https://stripe.com/",
  "credits": { "charged": true, "creditCost": 5, "refunded": false },
  "sourceZip": { "available": false, "unlocked": false, "creditCost": 100 },
  "statusUrl": "/api/v1/clone-requests/api_req_abc123"
}

Creating a live request costs 5 credits, charged immediately. Preview is free; downloading source is a separate, later action (Step 6).

Idempotency, the right way

The Idempotency-Key is your safety net for flaky networks:

  • Same key + same body → you get the same request back. Retry as often as you like; you're never double-charged and never get a duplicate job.
  • Same key + different body409 idempotency_conflict. The key is bound to the first payload it saw (including the callbackUrl).

Use something stable and meaningful, like order:${orderId} or a UUID you persist next to the work.

Step 5 — Wait for ready (poll or webhook)

A clone runs asynchronously and usually takes about 1–3 hours. There are two ways to learn when it's done.

Poll the status endpoint:

curl https://clonesite.ai/api/v1/clone-requests/api_req_abc123 \
  -H "x-api-key: cs_live_a1b2c3d4..."

The status moves through a small, predictable lifecycle, and the credit accounting is built in:

Request lifecycle: accepted to queued to processing to ready or failed, with a 5-credit charge on create, automatic refund on failure, and a 100-credit source unlock.

Five credits on create. Failed clones refund automatically. Preview stays free.

A ready response exposes the preview and whether source is available — but never a download URL:

{
  "id": "api_req_abc123",
  "mode": "live",
  "status": "ready",
  "previewUrl": "https://preview.clonesite.ai/...",
  "sourceZip": { "available": true, "unlocked": false, "creditCost": 100 }
}

If the clone fails, the status becomes failed and the 5 credits are refunded automatically — you don't pay for builds that don't land.

Or receive a webhook. If you passed a callbackUrl, Clonesite POSTs a signed event the moment the request reaches a terminal state:

event: clone.ready  |  clone.failed
{
  "event": "clone.ready",
  "id": "evt_abc123",
  "mode": "live",
  "cloneRequestId": "api_req_abc123",
  "templateSlug": "stripe-a1b2c3",
  "previewUrl": "https://preview.clonesite.ai/...",
  "sourceZip": { "available": true, "unlocked": false, "creditCost": 100 }
}

Verify the signature before trusting the body. Each delivery includes a timestamp and an HMAC over timestamp + "." + rawBody:

X-Clonesite-Timestamp: 1781946000
X-Clonesite-Signature: v1=<hex hmac_sha256>
import crypto from "node:crypto";

function verifyClonesiteWebhook(rawBody, headers, secret) {
  const ts = headers["x-clonesite-timestamp"];
  const signature = headers["x-clonesite-signature"]; // "v1=<hex>"
  const expected =
    "v1=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Webhooks are a notification, not the source of truth. Deliveries retry with backoff, and the body never carries the download URL — so even a leaked webhook log can't be used to pull your source. After receiving one, call the status endpoint to confirm.

Step 6 — Download the source (optional)

The source ZIP is a deliberate second step, taken after a human (or your agent) is happy with the preview. Unlocking it the first time costs 100 credits:

curl -X POST https://clonesite.ai/api/v1/clone-requests/api_req_abc123/source-downloads \
  -H "x-api-key: cs_live_a1b2c3d4..."
{
  "downloadUrl": "https://r2.clonesite.ai/signed-url",
  "expiresAt": 1781949600000,
  "filename": "stripe-a1b2c3-source.zip",
  "creditCost": 100,
  "alreadyUnlocked": false,
  "artifact": {
    "artifactId": "src_art_abc123",
    "templateSlug": "stripe-a1b2c3",
    "filename": "stripe-a1b2c3-source.zip",
    "contentType": "application/zip",
    "status": "ready",
    "checksumSha256": "9f86d081884c7d65...",
    "sizeBytes": 5242880,
    "createdAt": 1781949000000,
    "updatedAt": 1781949600000
  }
}

The URL is short-lived (about 5 minutes) — download or copy the ZIP to your own storage before expiresAt. Calling the endpoint again only re-signs a fresh URL; it does not charge another 100 credits once the request is unlocked.

Errors worth handling

The API returns a stable JSON error shape, so your agent can branch on error.code instead of parsing prose:

{ "error": { "code": "insufficient_credits", "message": "..." } }
  • 400invalid_request, missing_idempotency_key, or invalid_json: a bad body, a missing Idempotency-Key header, or unparseable JSON.
  • 401missing_api_key or invalid_api_key: no key, or a wrong or revoked key.
  • 402insufficient_credits: not enough credits to create or unlock.
  • 403insufficient_permissions or api_key_account_not_found: the key lacks the required permission, or its account is unavailable.
  • 404not_found: unknown request — also returned for a request that belongs to another account.
  • 409idempotency_conflict, request_not_ready, or source_artifact_not_ready: a reused key with a new body, or source requested before the clone (or its artifact) is ready.
  • 422invalid_callback_url or webhook_not_configured: a malformed callbackUrl, or one passed without active webhook settings.
  • 429rate_limited or usage_exceeded: slow down and honor the Retry-After header.
  • 500internal_error: an unexpected server error; retry with the same Idempotency-Key.

That's every code the API emits — openapi.json carries the same enumeration, so an agent can map each one without reading this page.

A revoked key starts returning 401 immediately, so rotating credentials is instant.

Built for agents: discovery

Here's the part that makes this an agent API rather than just a REST API: an agent doesn't need a human to read these docs. Hand it the key and two URLs, and it discovers the rest itself.

Agent discovery: from one key plus llms.txt and openapi.json, the agent constructs the Clone API calls with no human reading docs.

Hand it one key and two links; it reads the spec and constructs the calls.

  • llms.txt describes, in plain language, what Clonesite does, its limits, and how an agent should use it.
  • openapi.json is the machine-readable contract: preflight, free test-runs, live create, status polling, source downloads, the x-api-key scheme, and every error code.

Building an MCP server? Today the public API is REST + openapi.json. There is no hosted /mcp endpoint yet, but the endpoints map cleanly onto MCP tools (preflight, test-run, create, get, source-download) — so you can wrap them as your own MCP server and hand that to an agent instead.

Putting it together

Here's the whole live loop in one place — preflight, create, poll, download — with native fetch and no dependencies:

const BASE = "https://clonesite.ai/api/v1";
const KEY = process.env.CLONESITE_API_KEY;

async function cloneSite(sourceUrl, prompt, idempotencyKey) {
  // 1. Preflight (no side effects)
  const preflight = await api("/clone-requests/preflight", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sourceUrl, prompt }),
  });
  if (!preflight.canCreate)
    throw new Error(`preflight blocked: ${JSON.stringify(preflight.checks)}`);

  // 2. Create live request (spends 5 credits)
  let req = await api("/clone-requests", {
    method: "POST",
    headers: { "Idempotency-Key": idempotencyKey, "Content-Type": "application/json" },
    body: JSON.stringify({ sourceUrl, prompt }),
  });

  // 3. Poll until terminal
  while (req.status !== "ready" && req.status !== "failed") {
    await new Promise((r) => setTimeout(r, 5000));
    req = await api(`/clone-requests/${req.id}`);
  }
  if (req.status === "failed") throw new Error("clone failed (credits refunded)");

  // 4. Download source (spends 100 credits on first unlock)
  const dl = await api(`/clone-requests/${req.id}/source-downloads`, { method: "POST" });
  return dl.downloadUrl;
}

async function api(path, init = {}) {
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: { "x-api-key": KEY, ...(init.headers ?? {}) },
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${body.error?.code}`);
  return body;
}

That function is the entire integration. Give your agent a key, point it at this loop, and it can clone any public site into editable source on its own.

Create your key on /developers and let your agent take it from there.

Related guides