API docs

API reference.

Send a zip, get a live URL back. Plus signed webhooks and an MCP server for Claude. Start with the Quickstart.

Overview

Doope's public API has one job: take a .zip of a static website and publish it, live, in the same request. Doope unpacks, safety-scans, and publishes it while you wait, and the response carries the URL your project is already live at.

It publishes the same kind of project you get from dropping a file on the homepage, and you can call it from a script, a CI job, or your own tooling.

API host

Every API call goes to api.doope.sh, its own host apart from the Doope app at doope.sh. Projects you deploy are served from <slug>.doope.site.

Quickstart

Four steps from a built folder to a live URL.

1. Be on Solo or above

The deploy API is part of Solo, Pro, and Studio. On Free and Starter, a call to /api/v1/deploy returns 402 with upgrade: true; those plans publish from the homepage or from Claude over MCP. When a subscription ends, its API keys are revoked. See pricing for what each plan includes.

2. Create an API key

Open Settings → Developers, select Create key, and copy the key. It's shown once, so keep it in an environment variable or your CI's secrets:

export DOOPE_API_KEY=doope_your_key

3. Zip your build

Zip the contents of your build folder, so index.html sits at the root of the zip and becomes your homepage:

cd dist && zip -r ../site.zip . && cd ..

4. Deploy

curl --fail-with-body -X POST "https://api.doope.sh/api/v1/deploy?slug=my-site" \
  -H "Authorization: Bearer $DOOPE_API_KEY" \
  --data-binary @site.zip

The response carries your live URL. Run the same command again to publish a new version to the same slug. The Deploy reference covers every parameter, response, and error.

Deploy a project

Publishes a .zip of a static website. If ?slug= already points at a project you own, this redeploys it; otherwise a new project is created.

Endpoint

POSThttps://api.doope.sh/api/v1/deploy

Authenticated with an API key: send it as a bearer token (or X-Api-Key):

Authorization: Bearer doope_your_key

Example request

# deploy a zipped project in one curl (--fail-with-body exits non-zero on an error)
curl --fail-with-body -X POST "https://api.doope.sh/api/v1/deploy?slug=my-site" \
  -H "Authorization: Bearer $DOOPE_API_KEY" \
  --data-binary @site.zip

Parameters

slugquery, optionalName (or re-target) the project. Omit to get a freshly generated slug. If that slug already belongs to you, the deploy publishes a new version of it.
accountquery, optionalThe team account to publish to, if you can write to it. Defaults to the key owner's own account.
bodyraw bytes, requiredThe .zip to publish, sent as the raw request body (for example, curl --data-binary @site.zip).

Response

On success, 200:

{
  "url": "https://my-site.doope.site",
  "siteId": "site_...",
  "slug": "my-site",
  "status": "live"
}

Larger ZIPs return 202 with status: "processing". The URL becomes live after every batch is unpacked and scanned.

{
  "url": "https://my-site.doope.site",
  "siteId": "site_...",
  "slug": "my-site",
  "status": "processing"
}

To act the moment it goes live, subscribe to the site.published webhook (Pro and above).

Update & delete

Update: deploy again with the same ?slug=. Each deploy publishes a new version of that project at the same URL, and the dashboard keeps its recent versions ready to restore.

Delete: delete a project from its page in the dashboard, or from Claude with the MCP delete_project tool.

Errors

400Bad RequestInvalid slug, empty body, a zip over your plan's file or size limits, or a publish that failed. The error field says why.
401UnauthorizedMissing or invalid API key.
402Payment RequiredAPI deploys need Solo or above, or you've reached your project limit. The response includes upgrade: true.
403ForbiddenThe key owner needs write access to the target account.
409ConflictThat slug belongs to a project in another account.
413Payload Too LargeUpload exceeds your plan's max file size.
422UnprocessableThe safety scan flagged the content, so it stayed unpublished. The reason field says why.
429Too Many RequestsRate limit reached: 60 deploys an hour, shared across all of your keys.

Every error response is JSON with an error message you can log or show as-is.

Rate limits & sizes

Deploysper account60 an hour, shared across all of the account's keys.
Upload sizeper planEach plan's max upload size, listed on the pricing page.
Projectsper planDeploying to a new slug creates a project and counts toward your plan's project limit. Redeploys to a slug you own update that project in place.

Create an API key

Create API keys in the Doope app. Open Settings → Developers and select Create key (Solo and above). The key is shown once, right after you create it, so copy it somewhere safe. Doope stores only a hash of it.

Keys look like doope_<48 hex chars>. Revoke a key from Settings → Developers anytime. It stops working immediately.

Using your key

Authorization: Bearer doope_your_key

An X-Api-Key: doope_your_key header works too, if a Bearer header doesn't fit your tooling. Keep the key in server-side code and private config, and treat it like a password.

Any CI that runs curl can deploy. The Deploy section has a ready-to-paste GitHub Actions workflow.

Webhooks

Webhooks send your account a signed POST request the moment something happens on one of your projects. Available on Pro and above. Manage endpoints from Integrations in the app, which is also where an endpoint's signing secret is shown, once, right after you create it.

Events

site.publishedeventA version went live: a first publish, a redeploy, or a rollback to an earlier version.
lead.capturedeventA visitor sent the sign-up form on one of your projects.
pingtest event"Send test" in the dashboard, and the one-time verification ping sent when you add or re-point an endpoint. Every endpoint receives it automatically.

Endpoint requirements

An endpoint is a public https:// URL on a hostname you control (Doope's own domains, IP addresses, and private network addresses are reserved). Up to 5 endpoints per account. Before it receives real events, an endpoint is verified: creating (or re-pointing) one sends a signed ping whose body carries a one-time challenge, which you paste back into the dashboard to show that requests to that URL reach you. Any request inspector works for this, including Webhooks by Zapier's "Catch Hook" trigger, which is also how you wire Doope into a Zap.

Endpoints keep their configuration through plan changes, and deliveries run whenever the account is on Pro or above, starting with the next event.

Headers

Doope-EventheaderThe event type, e.g. site.published.
Doope-DeliveryheaderThe event's id. Stable across every retry of the same event, so it's what you dedupe on.
Doope-SignatureheaderHMAC-SHA256 over the timestamp and body. See Verifying signatures.

Events & payloads

Every delivery's body is JSON, shaped the same way regardless of event type: { id, type, created, data }. The id matches the Doope-Delivery header, and created is Unix seconds.

site.published

{
  "id": "evt_...",
  "type": "site.published",
  "created": 1730000000,
  "data": {
    "siteId": "site_...",
    "slug": "my-site",
    "url": "https://my-site.doope.site",
    "versionId": "ver_..."
  }
}

lead.captured

{
  "id": "evt_...",
  "type": "lead.captured",
  "created": 1730000000,
  "data": {
    "siteId": "site_...",
    "email": "[email protected]",
    "fields": { "name": "Vi" },
    "submittedAt": "2026-09-23T12:00:00.000Z"
  }
}

fields holds whatever extra inputs your capture form collected, or {} when there are none.

Verifying signatures

Every delivery carries a Doope-Signature header:

Doope-Signature: t=1730000000,v1=5257a869e...

t is the Unix timestamp the request was signed at, and v1 is a hex-encoded HMAC-SHA256(signing secret, "{t}.{raw body}"). The timestamp is part of what's signed, so a captured request can't be replayed later under a new one. Recompute the same HMAC over the exact bytes you received (before any JSON parsing), compare it to v1 in constant time, and reject anything where t is more than a few minutes from now.

Node example

// Node 18+. req.body must be the raw, unparsed request bytes: capture
// it with express.raw({ type: "application/json" }), not express.json().
import { timingSafeEqual, createHmac } from "node:crypto";

function verifyDoopeWebhook(header, rawBody, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && timingSafeEqual(given, want);
}

// in your route handler:
if (!verifyDoopeWebhook(req.header("Doope-Signature"), req.body, process.env.DOOPE_WEBHOOK_SECRET)) {
  return res.status(401).end();
}

Delivery & retries

A delivery counts as successful on any 2xx response. Any other response, redirects included, is retried with exponential backoff (roughly 30s, 2m, 8m, 32m, 2h, 8.5h, then 12h), up to 8 attempts over about 24 hours, after which the delivery is marked failed. Doope reads only the status code of your response. The dashboard's "Recent deliveries" list (attempt, status, response code, duration) covers the last 7 days.

Retries resend the exact same body, so use Doope-Delivery (stable across every attempt of one event) to dedupe on your side. For example, skip processing if you've already recorded that id.

Publish from Claude (MCP)

The Doope MCP server lets Claude, or any client that speaks the Model Context Protocol, list, publish, read, update, and delete your projects right from a conversation. Same account, same projects as the dashboard, and it works on every plan, Free included.

URLhttps://api.doope.sh/mcp

Connecting

Every client signs in with your Doope account (OAuth). Pick the steps for yours:

claude.aiOAuthSettings → Connectors → Add custom connector, paste the URL above, then sign in with your Doope account when prompted.
Claude CodeOAuthRun the command below, then run /mcp inside Claude Code and choose doope to sign in with your browser.
Cursor and other MCP clientsOAuthAdd https://api.doope.sh/mcp as a remote (Streamable HTTP) server. The client opens the Doope sign-in page on first use.
claude mcp add --transport http doope https://api.doope.sh/mcp

For a headless setup (CI, a shared server), connect with an API key from Settings → Developers instead (Solo and above). Send it as a bearer header:

claude mcp add --transport http doope https://api.doope.sh/mcp --header "Authorization: Bearer doope_your_key"

Manage connected apps anytime from Settings → Developers.

Available tools

list_projectsList your projects and their live URLs.
publish_projectPublish a new project from a set of files, or publish a new version of one of yours by passing its slug.
read_project_fileRead a text file (up to 2 MB) from one of your projects.
update_project_filesWrite, replace, or delete files in a project and publish the result as a new version.
delete_projectDelete one of your projects forever, along with its files and custom domain connection.