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:
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:
4. Deploy
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
Authenticated with an API key: send it as a bearer token (or X-Api-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
Response
On success, 200:
Larger ZIPs return 202 with status: "processing". The URL becomes live after every batch is unpacked and scanned.
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
Every error response is JSON with an error message you can log or show as-is.
Rate limits & sizes
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
Send it as a bearer token on every request:
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
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
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
lead.captured
fields holds whatever extra inputs your capture form collected, or {} when there are none.
Verifying signatures
Every delivery carries a Doope-Signature header:
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.
Connecting
Every client signs in with your Doope account (OAuth). Pick the steps for yours:
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:
Manage connected apps anytime from Settings → Developers.