drophere.cc
Instant static hosting for AI agents and developers.
drophere.cc lets you upload static files via a 3-step flow and get a live URL back in seconds. It's designed so AI agents (Claude, Cursor, etc.) can publish HTML, images, and other static assets programmatically.
Agent/User API R2 Storage
| | |
| 1. POST /api/v1/artifact | (file manifest) |
| ────────────────────────>| |
| <──── slug + upload URLs | |
| | |
| 2. PUT files to upload URLs |
| ────────────────────────>| stream to R2 ──────────────>|
| | |
| 3. POST /api/v1/artifact/:slug/finalize |
| ────────────────────────>| copy unchanged files ──────>|
| <──── live URL | |Step 1 — Create: Send a file manifest (paths, sizes, content types). Get back a unique slug and upload URLs.
Step 2 — Upload: PUT each file to its upload URL. The API streams the body directly to R2 storage.
Step 3 — Finalize: Tell the API you're done. It activates the artifact and returns a live URL like bold-canvas.drophere.cc.
For updates, only changed files need to be re-uploaded (hash-based incremental deploys).
Base URL
https://drophere.cc
All endpoints return JSON. Authenticated endpoints require an Authorization: Bearer <api_key> header.
Payment Paths
Drophere supports both account-backed publishing and accountless machine publishing. Choose the path by principal, not by payment brand.
| Principal | Publish path | Payment path |
|---|---|---|
| Human or signed-in agent | CLI, REST, or MCP with an API key | Stripe subscription and account billing |
| Anonymous human | Claim-token upload | Free temporary limits |
| Accountless agent | Machine artifact API | MPP Tempo pay-per-publish |
Do not show Stripe and Tempo as interchangeable checkout choices for the same publish request. Account-backed users use Stripe-backed billing. Accountless agents that need to pay per publish use the machine endpoints and satisfy the MPP tempo/charge challenge.
Agent-readable docs
If you're an agent or script, prefer the Markdown API reference instead of this HTML page:
https://drophere.cc/skill/references/API.md
For a compact machine-readable index first, fetch https://drophere.cc/api/v1/skill/docs. It returns capability groups plus markdownDocsUrl and htmlDocsUrl.
Installation
Claude Code Skill (recommended for AI agents)
# macOS / Linux
curl -fsSL https://drophere.cc/install.sh | bash
# Windows
irm https://drophere.cc/install.ps1 | iex
Then tell your agent: "publish this to drophere.cc"
Direct API usage
No SDK required. All operations are standard HTTP requests with JSON bodies. Use curl, fetch, or any HTTP client.
Quickstart
Publish a single HTML file in three commands. No account required.
1. Create an artifact
curl -X POST https://drophere.cc/api/v1/artifact -H "Content-Type: application/json" -d '{
"files": [
{ "path": "index.html", "size": 45, "contentType": "text/html" }
]
}'
{
"slug": "bold-canvas",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"siteUrl": "https://bold-canvas.drophere.cc/",
"uploads": [
{
"path": "index.html",
"method": "PUT",
"url": "https://drophere.cc/api/v1/upload/bold-canvas/550e84.../index.html",
"headers": { "Content-Type": "text/html" }
}
],
"expiresAt": "2026-03-13T10:00:00Z",
"limits": { "maxFileSize": 104857600, "maxArtifactSize": 262144000 },
"feedback": "https://drophere.cc/api/v1/feedback",
"claimToken": "a1b2c3d4...64chars"
}
2. Upload the file
curl -X PUT "UPLOAD_URL_FROM_STEP_1" -H "Content-Type: text/html" --data-binary '<h1>Hello from drophere.cc</h1>'
3. Finalize
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/finalize -H "Content-Type: application/json" -d '{
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"claimToken": "a1b2c3d4...64chars"
}'
{
"slug": "bold-canvas",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"siteUrl": "https://bold-canvas.drophere.cc/",
"expiresAt": "2026-03-13T10:00:00Z",
"feedback": "https://drophere.cc/api/v1/feedback"
}
Your site is now live at bold-canvas.drophere.cc. Anonymous uploads expire after 24 hours. Authenticate to make them permanent.
Save the claimToken. It's the only way to update or finalize anonymous artifacts. It cannot be recovered.
Authentication
drophere.cc uses magic link authentication. Request a verification code via email, exchange it for a permanent API key, then use the API key as a Bearer token for all authenticated requests.
Getting an API key
1. Request a code
curl -X POST https://drophere.cc/api/auth/agent/request-code -H "Content-Type: application/json" -d '{ "email": "you@example.com" }'
{
"success": true,
"requiresCodeEntry": true,
"expiresAt": "2026-03-12T10:15:00Z"
}
A code in XXXX-XXXX format is sent to your email. Codes expire after 15 minutes.
Rate Limit 1 request per email per 60 seconds.
2. Verify the code
curl -X POST https://drophere.cc/api/auth/agent/verify-code -H "Content-Type: application/json" -d '{ "email": "you@example.com", "code": "ABCD-EFGH" }'
Auth codes use the XXXX-XXXX format: eight uppercase letters/digits split by a hyphen. Verification accepts the hyphenated form shown in the email; agents should pass it through exactly as the user provides it.
{
"success": true,
"email": "you@example.com",
"apiKey": "a1b2c3d4e5f6...64chars",
"isNewUser": true
}
The code is single-use and deleted on successful verification. The apiKey is permanent — store it securely.
Refresh a browser session
The account dashboard automatically converts an existing Bearer-authenticated login into the host-wide, HttpOnly browser session used by owner-only controls on artifact subdomains. Users do not need to sign out or enter another email code.
curl -X POST https://drophere.cc/api/auth/browser/session -H "Authorization: Bearer <api_key>" -H "Origin: https://drophere.cc"
{
"success": true
}
The response refreshes the dh_account cookie for .drophere.cc. The cookie is HttpOnly and Secure; the API key is never placed in a URL or returned by this endpoint. Newly minted browser sessions are bound to the current API key and stop authenticating when that key is rotated. Missing or invalid API keys return 401, and a non-matching Origin returns 403.
3. Use the API key
Include the API key as a Bearer token in subsequent requests:
curl https://drophere.cc/api/v1/artifacts -H "Authorization: Bearer a1b2c3d4e5f6...64chars"
API key storage priority
If you're using the Claude Code skill, the API key is resolved in this order:
--api-keyflagDROPHERE_API_KEYenvironment variable- Credential file at
~/.drophere/credentials
Rotating your API key
If your key leaks, you can self-serve rotate it. The fastest path is the Rotate key button on drophere.cc/account. To do it programmatically:
curl -X POST https://drophere.cc/api/v1/me/api-key/rotate -H "Authorization: Bearer <current_key>"
{
"apiKey": "<new 64-hex key>",
"message": "API key rotated. All clients using the old key will start returning 401..."
}
The endpoint atomically replaces the key, fires a confirmation email to the account address (best-effort), and returns the new key in the response. The old key starts returning 401 immediately on every authenticated endpoint and on the MCP surfaces (/mcp, /mcp/<apiKey>).
Rate-limited to 5 rotations/hour/user. Not exposed via MCP — agents can't rotate their own credentials and lock you out.
Recovery if an attacker rotated first: re-run the magic-link flow (request-code → verify-code). The verify endpoint returns whatever key is currently in the database; rotate again from /account with that key. The magic-link channel is gated on email control, so the attacker can't follow.
Billing
Free Token includes API and MCP access, unlimited 24-hour artifacts, and 10 persistent artifacts. Unlimited unlocks unlimited persistent artifacts and custom artifact slugs. Unlimited Pro unlocks access control, password protection, collaboration, service variables, and custom domains, with up to 20 exact custom hostnames and 5 connected root authorities per account. Every exact domain row counts until deletion, including pending or failed registrations and children created through a shared connection. A connected root counts until retirement.
Plans
No Auth
{
"plans": [
{ "id": "unlimited", "price": "$4.99/month" },
{ "id": "secure", "price": "$9.99/month" }
]
}
Status
{
"plan": "free_token",
"usage": {
"persistentArtifacts": 3,
"persistentArtifactLimit": 10,
"customHostnames": 0,
"customHostnameLimit": 0,
"connectedRootAuthorities": 0,
"connectedRootAuthorityLimit": 0
},
"features": { "apiAndMcp": true, "secureAccessControls": false },
"upgradeOptions": [{ "plan": "unlimited", "checkoutEndpoint": "/api/v1/billing/checkout" }]
}
Checkout
curl -X POST https://drophere.cc/api/v1/billing/checkout -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "plan": "secure" }'
Creates a Stripe Checkout Session for unlimited or secure (Unlimited Pro). Agents should call this only after explicit user confirmation.
Portal
Creates a Stripe billing portal session for the authenticated account.
Paywall errors
Paid-feature gates return HTTP 402 with a structured PAYWALL response. Agents should present agentMessage, ask the human whether to upgrade, and create checkout only if the human confirms.
{
"error": "PAYWALL",
"code": "PLAN_REQUIRED",
"requiredPlan": "secure",
"agentMessage": "The collaboration feature requires the Unlimited Pro plan. Upgrade to Unlimited Pro ($9.99/month) at https://drophere.cc/account?upgrade=secure.",
"upgrade": { "checkoutEndpoint": "/api/v1/billing/checkout" },
"retry": { "action": "collaboration" }
}
Machine Payments
Machine payments are an accountless paid publish path for agents. Use the regular /api/v1/artifact endpoints for authenticated accounts, anonymous claim-token uploads, and Stripe subscription features. Use the machine endpoints only when the client can satisfy an MPP tempo/charge challenge.
Choose the path by principal, not by payment brand: logged-in humans and account-backed agents use the normal artifact APIs and Stripe-backed billing entitlements; accountless agents that need to pay per publish use the machine APIs and Tempo MPP. A client should not show both as interchangeable checkout choices for the same publish request.
Drophere v1 advertises Tempo push mode only: the client broadcasts the TIP-20 transfer, then retries with Authorization: Payment ... containing a transaction hash. Drophere verifies the transaction receipt, creates a machine-owned artifact, and returns a machine token for upload finalization.
No Auth Requires Idempotency-Key. Use a high-entropy idempotency key and treat it as sensitive until machineToken is stored. Drophere rejects machine idempotency keys unless they are 32-200 URL-safe characters (A-Z, a-z, 0-9, ., _, ~, -). Missing payment returns 402 Payment Required with WWW-Authenticate: Payment ... and Cache-Control: no-store.
Rate limits return 429 with RATE_LIMITED and Retry-After. Challenge requests are limited separately from paid verification retries.
Request body
{
"files": [
{ "path": "index.html", "size": 1024, "contentType": "text/html" }
],
"viewer": { "title": "Paid Site" },
"source": "agent"
}
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="...", realm="drophere.cc", method="tempo", intent="charge", request="...", expires="..."
Cache-Control: no-store
{
"error": "Payment required",
"code": "PAYMENT_REQUIRED",
"agentMessage": "Parse the WWW-Authenticate Payment challenge, broadcast the requested Tempo push transfer, then retry the same request with Authorization: Payment.",
"nextAction": "tempo_push_payment_then_retry",
"docsUrl": "https://docs.drophere.cc/#machine-payments"
}
Paid retries send Authorization: Payment ... with a base64url-encoded JSON credential. Reuse the challenge fields from WWW-Authenticate; keep challenge.request in the encoded form from that header.
{
"challenge": {
"id": "...",
"realm": "drophere.cc",
"method": "tempo",
"intent": "charge",
"request": "<base64url challenge request>",
"description": "Drophere paid machine artifact publish",
"expires": "2026-06-23T00:00:00.000Z"
},
"payload": { "type": "hash", "hash": "0x..." },
"source": "did:pkh:eip155:4217:0x..."
}
{
"slug": "paid-demo",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"siteUrl": "https://paid-demo.drophere.cc/",
"uploadUrlExpiresIn": 600,
"machineUploadExpiresAt": "2026-06-23T00:00:00.000Z",
"machineToken": "<secret capability token>",
"uploads": [{ "method": "PUT", "url": "https://drophere.cc/api/v1/upload/..." }]
}
Upload URLs use the same 10-minute proxy window as regular artifacts. If an upload URL expires before the paid machine upload deadline, call the machine refresh endpoint to get fresh URLs for missing files. expiresAt is returned after finalize, when the active artifact TTL starts.
Finalize a paid machine artifact after uploading every file. Send X-Drophere-Machine-Token and { "versionId": "..." }. Generic claim-token and account finalize endpoints reject machine-owned artifacts.
Reissue upload URLs for missing pending files while the machine upload window is still valid. Requires X-Drophere-Machine-Token.
Mark a paid machine artifact deleted and atomically queue durable KV/R2 cleanup. Requires X-Drophere-Machine-Token. Returns { "ok": true, "slug": "paid-demo", "storageCleanup": "pending" }; the one-minute cleanup worker retries until both stores are clear.
Create Artifact
Start a new artifact upload. Send a file manifest and receive upload URLs.
Auth Optional Anonymous uploads (no auth) get a 24-hour TTL and a claimToken.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
files | FileManifest[] | Yes | Array of file entries. Must be non-empty. |
files[].path | string | Yes | Relative file path. Must not contain .. |
files[].size | number | Yes | Exact file size in bytes |
files[].contentType | string | Yes | MIME type (e.g., text/html) |
files[].hash | string | No | SHA-256 hash for incremental deploys |
ttlSeconds | number | No | Custom TTL (authenticated only). Anonymous is always 24h. |
slug | string | No | Paid vanity artifact URL slug. Authenticated persistent artifacts only. Lowercase letters, numbers, and hyphens, 2-63 chars, no leading/trailing hyphen. |
lang | string | No | Language for generated slug words: en (default) or es. Always validated (invalid values return 400 INVALID_LANG) but has no effect when slug is provided. |
viewer | object | No | Optional {title?, description?, ogImagePath?, spaMode?, markdownDownload?} for auto-viewer and artifact behavior |
source | string | No | Origin identifier (e.g., cli, browser, api). Also read from x-drophere-client header. Max 100 chars. |
All viewer fields are optional. Defaults: no title or description, no ogImagePath, spaMode=false, and markdownDownload=false.
URL intent rule: when a user asks for https://name.drophere.cc/ or any *.drophere.cc root URL for one site, create the artifact with slug: "name". Do not claim or rename a handle, do not register name.drophere.cc as a custom domain, and do not fall back to handle.drophere.cc/name unless the user approves that fallback.
Vanity artifact URLs: Paid Unlimited and Unlimited Pro accounts may pass slug at creation time to publish at https://{slug}.drophere.cc/. Custom slugs require authenticated persistent artifacts; do not combine slug with ttlSeconds. Slugs must be lowercase DNS labels: a-z, 0-9, hyphen, 2-63 chars, no leading/trailing hyphen. Reserved platform names such as admin, api, www, docs, app, login, status, and support are blocked. On 409 with code: "CUSTOM_SLUG_UNAVAILABLE", ask the user for a different slug; do not invent one unless the user requested suggestions.
Generated slugs: are two words, e.g. pure-haze (lang: "en") or pulpo-bailarin (lang: "es"). After a generated candidate collides, the next retry samples a fresh word pair and adds a numeric suffix (pulpo-bailarin-7), growing it by one digit per retry.
curl -X POST https://drophere.cc/api/v1/artifact -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{
"files": [
{ "path": "index.html", "size": 2048, "contentType": "text/html", "hash": "sha256:abc123..." },
{ "path": "style.css", "size": 512, "contentType": "text/css", "hash": "sha256:def456..." }
],
"viewer": {
"title": "My Project",
"description": "A demo page"
},
"slug": "client-demo"
}'
{
"slug": "bold-canvas",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"siteUrl": "https://bold-canvas.drophere.cc/",
"expiresAt": null,
"uploads": [
{
"path": "index.html",
"method": "PUT",
"url": "https://drophere.cc/api/v1/upload/bold-canvas/550e84.../index.html",
"headers": { "Content-Type": "text/html" }
},
{
"path": "style.css",
"method": "PUT",
"url": "https://drophere.cc/api/v1/upload/bold-canvas/550e84.../style.css",
"headers": { "Content-Type": "text/css" }
}
],
"limits": {
"maxFileSize": 1073741824,
"maxArtifactSize": 5368709120
},
"feedback": "https://drophere.cc/api/v1/feedback"
}
uploads— upload URLs (10-minute window). Upload each file withPUT.claimToken— only returned for anonymous uploads. Store it to update/finalize later.expiresAt—nullfor authenticated uploads without a TTL.limits— current size limits so clients can pre-validate.feedback— URL for submitting feedback about the service.
REST create/update responses use uploads for direct HTTP PUTs. MCP create/update tools instead return mcpUploads, directHttpUploads, and nextStep; MCP clients should follow mcpUploads.
Rate Limit Authenticated: 60 creates/hour. Anonymous: 5 creates/hour per IP.
Errors
| Status | Code | Error |
|---|---|---|
400 | Invalid file manifest (missing fields, .. in path, empty array) | |
400 | CUSTOM_SLUG_INVALID | slug is not a valid lowercase DNS label or is reserved |
400 | CUSTOM_SLUG_REQUIRES_PERSISTENT_ARTIFACT | slug was combined with ttlSeconds; vanity slugs are persistent only |
400 | INVALID_LANG | lang was not one of the supported values (en, es) |
402 | ACCOUNT_REQUIRED | A signed-in account is required before claiming a vanity artifact slug |
402 | PLAN_REQUIRED | The signed-in account does not have custom_artifact_slugs |
409 | CUSTOM_SLUG_UNAVAILABLE | The slug is already used by an artifact, retained reservation, or handle |
413 | File or total artifact size exceeds limit | |
429 | Rate limit exceeded |
Upload Files
Upload each file to the URL returned in the uploads array. The API streams the body to R2 storage.
curl -X PUT "UPLOAD_URL_FROM_CREATE" -H "Content-Type: text/html" --data-binary @index.html
{
"ok": true,
"path": "index.html"
}
- Set the
Content-Typeheader as specified in the upload entry'sheaders - Upload URLs expire after 10 minutes
- The exact version must still be the artifact's pending version. Historical
abandonedversions cannot receive uploads, even inside the original time window Content-Lengthis required and must exactly match the declared file size in the manifest- Each version file is first-write-wins. Retries never replace stored bytes and return
409if the existing object's size or content type conflicts with the manifest - Upload all files before calling finalize
Upload URLs are time-limited. Complete all uploads within 10 minutes of creating the artifact.
Finalize Artifact
Mark an upload as complete and save an immutable version. By default the saved version is also activated and made accessible at its URL; owners and edit grants can set activate: false to save it without changing the live site. For incremental updates, unchanged files are copied server-side during finalization.
Authenticated Requires Bearer token, claimToken in body for anonymous artifacts, or an edit grant token via X-Drophere-Edit-Token.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
versionId | string | Yes | Must match the versionId from create/update |
claimToken | string | No | Required for anonymous artifacts |
activate | boolean | No | Defaults to true. Set false to save without changing the live version (owners/edit grants only). |
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/finalize -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "versionId": "550e8400-e29b-41d4-a716-446655440000", "activate": false }'
Finalizing always validates, copies, and saves an immutable version. Existing clients that omit activate continue to save and publish immediately. Owners and edit grants can save first, then deploy explicitly. Anonymous claim-token uploads must publish immediately.
{
"slug": "bold-canvas",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"siteUrl": "https://bold-canvas.drophere.cc/",
"state": "saved",
"isCurrent": false,
"savedAt": "2026-07-24T12:03:00.000Z",
"currentVersionId": "previous-live-version-id",
"expiresAt": null,
"feedback": "https://drophere.cc/api/v1/feedback"
}
Errors
| Status | Error |
|---|---|
400 | versionId is required |
401 | Authentication required |
403 | Invalid or missing claim token / not the artifact owner |
404 | Artifact or version not found |
409 | versionId does not match pending version, or edit grant base version changed |
Update Artifact (Incremental Deploy)
Update an existing artifact. Files with matching SHA-256 hashes are skipped — no re-upload needed. Only changed or new files get upload URLs.
Authenticated Requires Bearer token, claimToken in body, or an edit grant token via X-Drophere-Edit-Token.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
files | FileManifest[] | Yes | Complete manifest for owners/claim tokens; changed or new paths for edit-token merge updates |
claimToken | string | No | Required for anonymous artifacts |
baseVersionId | string | No | Required for edit grant updates; must match the current live version |
deletePaths | string[] | No | Edit tokens only. Explicit paths to remove; omission never deletes files |
manifestMode | merge | replace | No | Edit tokens only. Defaults to safe merge; replace opts into a complete-manifest update |
summary | string | No | Optional deploy summary for version history |
curl -X PUT https://drophere.cc/api/v1/artifact/bold-canvas -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{
"files": [
{ "path": "index.html", "size": 3072, "contentType": "text/html", "hash": "sha256:new123..." },
{ "path": "style.css", "size": 512, "contentType": "text/css", "hash": "sha256:def456..." }
]
}'
{
"slug": "bold-canvas",
"versionId": "660e8400-e29b-41d4-a716-446655440001",
"siteUrl": "https://bold-canvas.drophere.cc/",
"uploads": [
{
"path": "index.html",
"method": "PUT",
"url": "https://drophere.cc/api/v1/upload/bold-canvas/660e84.../index.html",
"headers": { "Content-Type": "text/html" }
}
],
"skipped": [
{ "path": "style.css", "hash": "sha256:def456..." }
],
"limits": { "maxFileSize": 1073741824, "maxArtifactSize": 5368709120 },
"feedback": "https://drophere.cc/api/v1/feedback"
}
Edit-token updates are merge-safe by default: supplied paths are changed or added, omitted paths carry forward, and deletion requires deletePaths. Use manifestMode: "replace" only for an explicit full-manifest replacement. Every edit-token update must send the real baseVersionId.
- Only files in
uploadsneed to be uploaded - Files in
skippedmatched by hash and will be copied server-side during finalize - After uploading changed files, finalize the new version
Errors
| Status | Error |
|---|---|
403 | Invalid claim token or not the artifact owner |
404 | Artifact not found |
409 | Base version changed; recreate update from current version |
410 | Artifact has expired |
413 | File or total artifact size exceeds limit |
Artifact Edit Grants
Owner-managed scoped tokens for collaborative publishing. deploy grants are write-only credentials for callers with a complete local source tree. editor grants add manifest, raw-source, and comment reads for safe token-only editing. Neither kind can delete the artifact, rollback, change access or passwords, mutate comments, manage variables, route handles or domains, duplicate artifacts, or create/revoke grants.
The raw token is returned only once on creation. Drophere stores only a token hash.
Create Edit Grant
Authenticated Artifact owner Bearer token.
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/edit-grants -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" -d '{ "name": "website editor", "kind": "editor", "ttlSeconds": 86400 }'
{
"grant": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "bold-canvas",
"name": "website editor",
"kind": "editor",
"scopes": ["deploy", "manifest:read", "source:read", "comments:read"],
"expiresAt": "2026-06-29T12:00:00.000Z",
"revokedAt": null,
"lastUsedAt": null
},
"token": "deg_secret_returned_once",
"tokenReturnedOnce": true
}
List Edit Grants
Authenticated Artifact owner Bearer token. Token values are never returned.
Revoke Edit Grant
Authenticated Artifact owner Bearer token. Revocation is idempotent.
Edit Context and Raw Source
Edit token All deploy grants receive currentVersionId, file count, and capability flags. The authoritative manifest requires manifest:read.
{
"slug": "bold-canvas",
"currentVersionId": "version-id",
"fileCount": 2,
"capabilities": {
"deploy": true, "readManifest": true,
"readSource": true, "readComments": true
},
"files": [{
"path": "index.html", "size": 26845,
"contentType": "text/html", "hash": "sha256:..."
}]
}
files is present only with include=manifest. Deploy-only grants can obtain currentVersionId but cannot request the manifest.
Editor token Streams stored R2 bytes before collaboration or Markdown transformations. Supports HTTP byte ranges, bounded startLine/endLine JSON reads, and explicit full-text format=json.
Line-range JSON returns slug, currentVersionId, path, startLine, endLine, totalLines, and text. Full-text JSON returns contentType, bytes, and complete UTF-8 text. Raw responses include ETag, Content-Length, Accept-Ranges, and Cache-Control: private, no-store; byte ranges return 206 or 416.
Literal search returns slug, currentVersionId, query, bounded matches with line/column/context fields, totalMatches, truncated, scannedBytes, skipped, skippedCount, and skippedTruncated. It returns at most 20 matches, 20 skipped-file details, and 32 KiB of context while scanning at most 2 MiB. When truncated, narrow by path.
Token-Efficient Text Edits
{
"baseVersionId": "current-version-id",
"summary": "Address heading feedback",
"operations": [{
"op": "replace_text",
"path": "index.html",
"expected": "Current heading",
"replacement": "New heading",
"requireMatches": 1
}]
}
Drophere applies exact replacements to stored UTF-8 source, computes the new hash, writes only changed objects, and carries every other file forward. Zero, ambiguous, or unexpected match counts return 409 without creating a pending version. Finalize the returned versionId normally.
{
"slug": "bold-canvas",
"versionId": "pending-version-id",
"baseVersionId": "current-version-id",
"readyToFinalize": true,
"changedFiles": [{
"path": "index.html", "replacements": 1,
"previousBytes": 26845, "bytes": 26831, "hash": "sha256:..."
}],
"copiedFileCount": 11,
"siteUrl": "https://bold-canvas.drophere.cc/"
}
Only one pending version may exist. Use the currentVersionId observed during search/read; stale live or pending state returns 409. Limits: 2 MiB per text file, 20 operations, 256 KiB replacement input, 512 KiB request body, 8 MiB changed output, and 60 edit creations per grant per hour.
Editor grants can also read GET /api/v1/artifact/:slug/comments?status=open&limit=20&messageLimit=20 without browser collaboration cookies or origin headers. Comment access remains read-only and defaults to 20 threads and 20 messages per thread. Pass the opaque pagination.nextCursor back as cursor for stable thread pagination. Each thread returns its root feedback plus newest replies and explicit messagePage.hasMore / attachment-truncation metadata.
| Status | Editor errors |
|---|---|
400 | Invalid search, context, source path, line range, edit request, or operation |
401 | EDIT_TOKEN_REQUIRED |
403 | Missing scope, deploy capability, or owner mismatch |
404 | Artifact, manifest file, or source object not found |
409 | Inactive artifact, stale base, existing pending version, changed state, or exact-match conflict |
410 | ARTIFACT_EXPIRED |
413 | Non-text, per-file, request, aggregate output, or artifact size limit |
416 | INVALID_BYTE_RANGE |
429 | EDIT_RATE_LIMITED |
500 | Missing current version or version creation failure |
502 | EDIT_STORAGE_FAILED |
These collaborator operations are REST/CLI-only, including batched operation files. The MCP server remains owner-authenticated so delegated edit-token authority cannot be confused with artifact-owner authority.
Version History
Authenticated Artifact owner Bearer token. Lists version records with base version, actor attribution, edit-grant label, summary, file count, save timestamp, isCurrent, previewUrl, previewExpiresAt, and computed uploading/saved/live/abandoned state. Only the record selected by pendingVersionId is uploading. An unfinalized historical record that is no longer pending is abandoned; it is terminal and cannot be finalized or discarded.
Every finalized saved or live version receives a fresh 24-hour signed preview URL. It serves that exact immutable file snapshot and its finalized viewer settings without changing the public live version. Deploy restores those same viewer settings, so preview and live match. Pre-preview historical versions are frozen once with their rollout-time viewer settings.
Preview URLs are bearer capabilities: share them only with intended reviewers. Existing password and restricted-email gates still apply, while the signed-in owner account can pass them. Preview responses are private, no-store, non-indexable, and suppress referrers. They serve static files and auto-viewers only; Store API, proxy, visits, quick edit, collaboration, and HTML-to-Markdown are unavailable. Non-finalized versions return null preview fields.
Deploy or Roll Back to a Saved Version
Authenticated Artifact owner Bearer token. Pass { "expectedCurrentVersionId": "current-version-id" }, or null when nothing is live. The expected value prevents stale owner actions. The selected version must be saved, and no upload may be pending. Selecting an older saved version performs a rollback through the same endpoint.
{
"versionId": "saved-version-id",
"state": "live",
"currentVersionId": "saved-version-id",
"previousVersionId": "current-version-id",
"siteUrl": "https://bold-canvas.drophere.cc/"
}
Publish With Edit Grant
curl -X PUT https://drophere.cc/api/v1/artifact/bold-canvas -H "X-Drophere-Edit-Token: deg_secret_returned_once" -H "Content-Type: application/json" -d '{ "baseVersionId": "current-version-id", "files": [{ "path": "index.html", "size": 2048, "contentType": "text/html", "hash": "sha256:new" }], "deletePaths": [] }'
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/finalize -H "X-Drophere-Edit-Token: deg_secret_returned_once" -H "Content-Type: application/json" -d '{ "versionId": "returned-version-id" }'
Supplied files change or add only those paths; omitted files carry forward automatically. Deletion requires deletePaths. Finalize succeeds only when the pending version was created by that grant and the artifact's live currentVersionId still matches the pending version's baseVersionId.
HTML Quick Edit Private Beta
HTML Quick Edit changes one visible static text node while preserving every other source byte, then publishes the result as a new immutable artifact version. It is server-gated and currently enabled only for verified @luzia.com Drophere accounts. The authenticated account must own the artifact.
The account library exposes edit text for eligible HTML artifacts. Agents and scripts use the same REST surface.
Resolve Account Features
Authenticated Bearer token or Drophere account session.
{ "features": { "htmlQuickEdit": true } }
Preview Text Edit
Authenticated Artifact owner Bearer token or same-origin Drophere account session.
{
"filePath": "index.html",
"baseVersionId": "current-version-id",
"locator": {
"elementPath": [
{ "tag": "main", "index": 0 },
{ "tag": "p", "index": 2 }
],
"textIndex": 0
},
"originalText": "Old quarterly target",
"replacementText": "New quarterly target",
"sessionId": "optional-client-session-id"
}
Element indices are zero-based among same-tag siblings. textIndex is zero-based among direct text-node children. Preview reads the immutable live source and returns a canonical before/after operation without writing a version.
Publish Text Edit
Uses the same body as preview, with optional summary. Publishing rechecks feature access, ownership, the source locator, and baseVersionId; copies unchanged files to a new immutable version; and atomically promotes it. Existing pending uploads are never overwritten.
{
"slug": "bold-canvas",
"versionId": "new-version-id",
"baseVersionId": "current-version-id",
"siteUrl": "https://bold-canvas.drophere.cc/",
"summary": "Quick edit: update visible text"
}
- HTML source is capped at 2 MiB; text is capped at 20,000 characters and cannot be empty.
- Runtime-generated and unsafe elements are refused; replacement text is HTML-escaped.
- Stale bases return
409 STALE_BASE_VERSION; existing drafts return409 PENDING_VERSION_EXISTS. - Publishing is limited to 30 operations per minute per user.
Claim Artifact
Transfer an anonymous artifact to your authenticated account. This removes the 24-hour expiry and the claim token, making the artifact permanent.
Authenticated
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/claim -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "claimToken": "a1b2c3d4...64chars" }'
{
"slug": "bold-canvas",
"siteUrl": "https://bold-canvas.drophere.cc/",
"message": "Artifact claimed successfully"
}
Claiming is idempotent — calling it again on an already-claimed artifact is a no-op.
Get Artifact
Retrieve details for a specific artifact, including its current files.
Authenticated Must own the artifact.
curl https://drophere.cc/api/v1/artifact/bold-canvas -H "Authorization: Bearer YOUR_API_KEY"
{
"slug": "bold-canvas",
"siteUrl": "https://bold-canvas.drophere.cc/",
"status": "active",
"currentVersionId": "550e8400-e29b-41d4-a716-446655440000",
"pendingVersionId": null,
"collaboration": {
"enabled": false,
"commentPolicy": "authenticated",
"commentDomain": null,
"commentAllowedEmails": null
},
"viewerMetadata": null,
"expiresAt": null,
"createdAt": "2026-03-12T10:00:00Z",
"updatedAt": "2026-03-12T10:01:00Z",
"files": [
{
"path": "index.html",
"size": 2048,
"contentType": "text/html",
"hash": "sha256:abc123..."
}
]
}
List Artifacts
List all artifacts owned by the authenticated user.
Authenticated
curl https://drophere.cc/api/v1/artifacts -H "Authorization: Bearer YOUR_API_KEY"
{
"artifacts": [
{
"slug": "bold-canvas",
"siteUrl": "https://bold-canvas.drophere.cc/",
"status": "active",
"currentVersionId": "550e8400-...",
"pendingVersionId": null,
"collaboration": {
"enabled": false,
"commentPolicy": "authenticated",
"commentDomain": null,
"commentAllowedEmails": null
},
"viewerMetadata": { "title": "My Project" },
"title": "My Project",
"expiresAt": null,
"updatedAt": "2026-03-12T10:01:00Z"
}
]
}
viewerMetadata is the full JSON blob set via the metadata endpoint (null when unset). title is a convenience extraction of viewerMetadata.title (trimmed; null when missing or empty) so list consumers don't have to dig. collaboration is included so owners and agents can discover whether the comment layer is enabled before calling the comment APIs.
Library
The private library is the owner rediscovery layer. Every authenticated artifact owned by the user appears automatically, including random artifact slugs and paid vanity artifact slugs. Anonymous artifacts appear after claim. Library routes are aliases; the artifact slug remains the immutable identity.
Human UI anchor: https://drophere.cc/account#library. Agents should use that exact URL when linking a user to the private library for rediscovery, organization, routing, or cleanup. Keep returning the specific artifact URL (artifactUrl or preferredUrl) when the user asks for the published site itself.
Authenticated
Query params: q, collectionId, tag, source, status, visibility, favorite=true, routed=true, archived=true, limit, cursor.
{
"items": [
{
"artifactSlug": "bold-canvas",
"artifactUrl": "https://bold-canvas.drophere.cc/",
"preferredUrl": "https://alice.drophere.cc/docs",
"routes": [
{ "namespaceType": "handle", "namespace": "alice", "location": "docs", "slug": "bold-canvas", "url": "https://alice.drophere.cc/docs" }
],
"title": "Launch docs",
"summary": "Customer-facing launch notes",
"tags": ["launch", "docs"],
"collections": [{ "id": "uuid", "name": "Launch", "slug": "launch" }],
"favorite": true,
"archived": false,
"sourceLabel": "mcp",
"status": "active",
"visibility": "public",
"updatedAt": "2026-03-11T10:01:00.000Z"
}
],
"nextCursor": null
}
Authenticated Accepts title, summary, tags, favorite, archived, and sourceLabel.
Related library endpoints: GET /api/v1/library/items/:artifactSlug/route-suggestions, GET /api/v1/library/items/:artifactSlug/related, GET/POST /api/v1/library/collections, PATCH/DELETE /api/v1/library/collections/:collectionId, and POST/DELETE /api/v1/library/collections/:collectionId/items. Collections are private to the authenticated user. Adding an item to a collection is idempotent and only works for artifacts owned by the same user.
Update Viewer Metadata
Update the title, description, or OG image for auto-viewer rendering. This metadata has no effect if the artifact contains an index.html or is a single HTML file — in both cases the HTML is served directly and its own <head> tags are authoritative.
Authenticated
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/metadata -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{
"viewerMetadata": {
"title": "Project Gallery",
"description": "Screenshots from v2 launch",
"ogImagePath": "hero.png"
}
}'
{
"slug": "bold-canvas",
"viewerMetadata": {
"title": "Project Gallery",
"description": "Screenshots from v2 launch",
"ogImagePath": "hero.png"
},
"note": "Viewer metadata updated successfully."
}
Artifact Tags
Private artifact-level tags support knowledge discovery and agent search. Tags are owner-only metadata and are not exposed on public artifact pages.
Tags are normalized by trimming, lowercasing, and collapsing whitespace or hyphens to -. Empty tags are rejected, each tag is limited to 40 characters, and each artifact can have at most 20 tags. PATCH /tags replaces the full tag set, so agents should read existing tags before preserving or extending them.
Get Artifact Tags
Authenticated Must own the artifact.
curl https://drophere.cc/api/v1/artifact/bold-canvas/tags -H "Authorization: Bearer YOUR_API_KEY"
{
"slug": "bold-canvas",
"tags": [
{ "tag": "strategy", "source": "agent", "confidence": 0.82, "createdAt": "2026-03-13T10:00:00Z", "updatedAt": "2026-03-13T10:00:00Z" }
],
"count": 1
}
Replace Artifact Tags
Authenticated Must own the artifact.
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/tags -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{
"tags": ["Strategy", "Q1 Plan"],
"source": "agent",
"confidence": 0.82
}'
source defaults to user for REST. Valid values are agent, user, and import. confidence is optional and must be between 0 and 1 when provided.
{
"slug": "bold-canvas",
"tags": [
{ "tag": "q1-plan", "source": "agent", "confidence": 0.82, "createdAt": "2026-03-13T10:00:00Z", "updatedAt": "2026-03-13T10:00:00Z" },
{ "tag": "strategy", "source": "agent", "confidence": 0.82, "createdAt": "2026-03-13T10:00:00Z", "updatedAt": "2026-03-13T10:00:00Z" }
],
"count": 2
}
List Tags
Authenticated
curl https://drophere.cc/api/v1/tags -H "Authorization: Bearer YOUR_API_KEY"
{
"tags": [
{ "tag": "strategy", "count": 4 },
{ "tag": "q1-plan", "count": 1 }
],
"count": 2
}
Delete Artifact
Permanently delete an artifact and all its versions. Files are removed from R2 storage in the background.
Authenticated Must own the artifact.
curl -X DELETE https://drophere.cc/api/v1/artifact/bold-canvas -H "Authorization: Bearer YOUR_API_KEY"
{
"slug": "bold-canvas",
"message": "Artifact deleted"
}
Password Protection
Protect an artifact with a password. Simpler alternative to email-based access control. Visitors see a password form; correct entry sets a 30-day session cookie.
Set or remove password
Authenticated Must own the artifact.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
password | string or null | Yes | 8-128 chars to set, null to remove |
# Set a password
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/password \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "password": "my-secret-123" }'
{
"slug": "bold-canvas",
"passwordProtected": true
}
# Remove password
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/password \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "password": null }'
Security: Passwords are stored as bcrypt hashes (cost factor 10). Changing or removing a password invalidates all existing sessions. Password attempts are rate-limited to 10/minute/IP.
Password protection is checked before email-allowlist access control. Both can be active simultaneously.
Collaboration
Enable an isolated reader-style highlight and comment layer on HTML artifacts. Artifact visibility, passwords, and email gates still control who can view the artifact; the collaboration comment policy controls who can write comments.
Enable or disable collaboration
Authenticated Must own the artifact.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Enable or hide the collaboration layer. |
commentPolicy | string | No | authenticated, anyone, same_domain, or specific_accounts. |
commentDomain | string or null | No | Required for same_domain unless inferred from the owner's non-consumer email domain. |
commentAllowedEmails | string[] or null | No | Required for specific_accounts. These must be Drophere account emails. |
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/collaboration \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'
{
"slug": "bold-canvas",
"collaborationEnabled": true,
"commentPolicy": "authenticated",
"commentDomain": null,
"commentAllowedEmails": null
}
Set view and comment permissions atomically
Authenticated Must own the artifact. This endpoint validates view access and comment settings before writing, updates both together, and returns the persisted readback.
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/permissions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"access": { "visibility": "public" },
"collaboration": {
"enabled": true,
"commentPolicy": "specific_accounts",
"commentAllowedEmails": ["alice@example.com"]
}
}'
{
"slug": "bold-canvas",
"access": {
"visibility": "public",
"allowedEmails": null,
"allowedDomains": null
},
"collaboration": {
"enabled": true,
"commentPolicy": "specific_accounts",
"commentDomain": null,
"commentAllowedEmails": ["alice@example.com"]
}
}
Read comments
Authenticated Owner Bearer token. Viewer UI uses the isolated Drophere frame after artifact access is granted. Query parameter status may be open, resolved, or all. settings.viewer.canComment and each thread's capabilities.canReply reflect the current viewer's comment policy eligibility.
curl https://drophere.cc/api/v1/artifact/bold-canvas/comments?status=open \
-H "Authorization: Bearer YOUR_API_KEY"
Comment actions
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/comments/THREAD_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "resolved" }'
Viewer commenting runs through an isolated Drophere-controlled frame using a short-lived HttpOnly access cookie. The frame can create threads, reply, paste image attachments, and delete the viewer's own messages when the artifact gates and comment policy allow it. Artifact JavaScript does not receive comment bodies or private author metadata. Owner and agent APIs receive private author metadata; viewer responses redact email and user IDs. Attachments are validated and served through artifact gates rather than direct public bucket URLs.
Agents should prefer the MCP tools for parity: drophere_set_collaboration, drophere_list_comments, drophere_add_comment, drophere_update_comment, and drophere_delete_comment.
GET /api/v1/artifact/:slug/annotations and PATCH /api/v1/artifact/:slug/annotations/:id remain as temporary owner-only compatibility aliases backed by comment threads.
Duplicate Artifact
Create a server-side copy of an artifact with a new slug. Files are copied within R2 — no re-upload needed. The destination remains non-live while create-only copies validate source size and content type against the manifest, then becomes live in one atomic database transition. A failed copy returns no live URL and best-effort removes its guarded pending database state and staged objects. With a client request ID, the exact staged destination remains retryable for up to 24 hours; after the fixed deadline and 15 minutes of inactivity, a bounded minute reaper removes only the exact still-pending duplicate and queues durable object cleanup.
Authenticated Must own the source artifact.
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/duplicate \
-H "Authorization: Bearer YOUR_API_KEY"
{
"slug": "calm-reef",
"sourceSlug": "bold-canvas",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"siteUrl": "https://calm-reef.drophere.cc/",
"files": 12
}
The duplicate does NOT copy: password, access control settings, TTL/expiry, domain links, or claim token. The new artifact starts as public with no expiry.
Rate Limit Same as artifact creation (60/hour authenticated).
Refresh Upload URLs
Re-issue upload URLs for a pending version when the original 10-minute window expires. Only returns URLs for files not yet uploaded.
Auth Optional Authenticated via Bearer token, or anonymous via claimToken in body.
curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/uploads/refresh \
-H "Authorization: Bearer YOUR_API_KEY"
{
"slug": "bold-canvas",
"versionId": "550e8400-e29b-41d4-a716-446655440000",
"uploads": [
{
"path": "app.js",
"method": "PUT",
"url": "https://drophere.cc/api/v1/upload/...",
"headers": { "Content-Type": "application/javascript" }
}
],
"alreadyUploaded": ["index.html", "style.css"],
"expiresIn": 600
}
- Only works when a pending (unfinalized) version exists
- Resets the 10-minute upload window
- Files already in R2 are listed in
alreadyUploadedand skipped
Access Control
Restrict who can view an artifact by email address or email domain. By default, all artifacts are public.
Set Visibility
Authenticated Must own the artifact.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
visibility | string | Yes | "public" or "restricted" |
allowedEmails | string[] | No | Up to 100 email addresses |
allowedDomains | string[] | No | Up to 20 domain names |
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/access -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{
"visibility": "restricted",
"allowedEmails": ["alice@acme.com", "bob@acme.com"],
"allowedDomains": ["acme.com"]
}'
{
"slug": "bold-canvas",
"visibility": "restricted",
"allowedEmails": ["alice@acme.com", "bob@acme.com"],
"allowedDomains": ["acme.com"]
}
To make public again, set visibility to "public" — this clears all allowlists.
Consumer domains are blocked. You cannot use gmail.com, outlook.com, yahoo.com, hotmail.com, icloud.com, aol.com, protonmail.com, or proton.me as allowed domains. Use specific email addresses instead.
When visibility is "restricted", at least one email or domain must be provided.
Errors
| Status | Error |
|---|---|
400 | Invalid visibility, email, or domain format |
400 | Consumer domain blocked |
400 | At least one email or domain required for restricted |
403 | Not the artifact owner |
404 | Artifact not found |
410 | Artifact has expired |
Get Access Control
Authenticated Must own the artifact.
curl https://drophere.cc/api/v1/artifact/bold-canvas/access -H "Authorization: Bearer YOUR_API_KEY"
{
"slug": "bold-canvas",
"visibility": "restricted",
"allowedEmails": ["alice@acme.com"],
"allowedDomains": ["acme.com"]
}
Visitor Authentication
Visitors to restricted artifacts verify their email via a one-time code. After verification, a session cookie (dh_visitor) is set on .drophere.cc for 30 days.
Request visitor code
No Auth
curl -X POST https://drophere.cc/api/v1/visitor/request-code -H "Content-Type: application/json" -d '{ "email": "alice@acme.com", "slug": "bold-canvas" }'
{
"success": true,
"expiresIn": 900
}
Timing-safe response. If the email is not on the allowlist, the endpoint still returns 200 but does not send a code. This prevents probing which emails have access.
Verify visitor code
curl -X POST https://drophere.cc/api/v1/visitor/verify-code -H "Content-Type: application/json" -d '{ "email": "alice@acme.com", "code": "ABCD-EFGH", "slug": "bold-canvas" }'
{
"success": true,
"email": "alice@acme.com"
}
Sets a dh_visitor cookie (30-day TTL, HttpOnly, Secure, SameSite=None).
URL Structure
Every artifact gets a unique subdomain URL. You can also serve artifacts via handles and custom domains.
| Pattern | Resolution |
|---|---|
{slug}.drophere.cc | Direct artifact access |
{handle}.drophere.cc | Handle root link |
{handle}.drophere.cc/{location} | Longest prefix match against handle's links |
{custom-domain}/{location} | Same as handle but for custom domain |
Handles
Handles give you a custom subdomain: yourname.drophere.cc. Combine with links to map paths to different artifacts.
Handle vs. vanity artifact slug
Both handles and vanity artifact slugs use the same {name}.drophere.cc subdomain namespace, but they are different products.
| Feature | URL shape | Purpose | Limits |
|---|---|---|---|
| Vanity artifact slug | https://client-demo.drophere.cc/ | One artifact gets the root subdomain directly | Paid persistent artifact, chosen only at creation time |
| Handle | https://acme.drophere.cc/docs | Account namespace that routes paths to one or more artifacts | One handle per account |
Decision rule for agents:
- User asks for
https://name.drophere.cc/orname.drophere.ccfor one artifact/site: create a persistent artifact withslug: "name". - User asks for
https://handle.drophere.cc/path: use the existing handle plus a link, or claim a handle only if the user explicitly wants an account namespace. - User asks for
example.comor another non-Drophere hostname: use custom domains.
Do not claim or rename a handle when the user asks for a vanity artifact URL. Use the slug field on artifact creation instead.
The namespace is exclusive. A name already used by an artifact slug, retained vanity slug reservation, or handle cannot be claimed by the other mechanism. New generated artifact slugs also skip retained vanity reservations and handles, so there is no runtime precedence to choose for new claims. If a request conflicts, REST returns 409 with either CUSTOM_SLUG_UNAVAILABLE or HANDLE_UNAVAILABLE; agents should ask the user for the next name.
Claim handle
Authenticated
curl -X POST https://drophere.cc/api/v1/handle -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "handle": "alice" }'
{
"handle": "alice",
"hostname": "alice.drophere.cc",
"namespace_id": "user-uuid"
}
Validation: 2-30 chars, lowercase alphanumeric + hyphens, no leading/trailing hyphens.
Reserved handles: admin, api, www, help, blog, docs, app, dashboard, settings, account, login, signup, auth, status, support.
Errors
| Status | Code | Error |
|---|---|---|
400 | HANDLE_INVALID | Invalid handle format |
409 | HANDLE_ALREADY_SET | The account already has one handle. The response includes the current handle, hostname, and nextAction pointing single-site root URL requests to artifact slug. |
409 | HANDLE_UNAVAILABLE | The handle name is already used by a handle, artifact slug, or retained vanity reservation |
Get handle
Authenticated
curl https://drophere.cc/api/v1/handle -H "Authorization: Bearer YOUR_API_KEY"
{
"handle": "alice",
"hostname": "alice.drophere.cc",
"namespace_id": "user-uuid",
"links": [
{ "location": "", "slug": "bold-canvas" },
{ "location": "blog", "slug": "quiet-river-b3m1" }
]
}
Change handle
Authenticated
curl -X PATCH https://drophere.cc/api/v1/handle -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "handle": "new-name" }'
{
"handle": "new-name",
"hostname": "new-name.drophere.cc"
}
Release handle
Authenticated
Deletes the handle, its KV entry, and all associated links.
curl -X DELETE https://drophere.cc/api/v1/handle -H "Authorization: Bearer YOUR_API_KEY"
{
"success": true
}
Custom Domains
Serve artifacts from your own domain. Register the domain, add a DNS record, then create links to route paths to artifacts.
Agents can manage standalone and connected-domain lifecycles through the domain MCP tools. These tools use the same authenticated operations and return the same success and error fields as the REST endpoints below.
Connect once for automatic subdomains
Authenticated Connects a registrable root domain. V1 rejects delegated subzones so subzone control cannot monopolize the root owner's authority. The response contains a public ownership TXT and a wildcard CNAME. The wildcard must be DNS only. Drophere never edits DNS, replaces an existing wildcard, or changes exact records.
Unlimited Pro allows 5 non-retired connected roots per account. Admission is serialized with creation; retrying an already-counted root remains available at the limit. A new sixth root returns 409 CONNECTED_DOMAIN_LIMIT_REACHED.
curl -X POST https://drophere.cc/api/v1/domain-connections -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "domain": "example.com" }'
{
"base_domain": "example.com",
"registrable_domain": "example.com",
"state": "pending",
"ready": false,
"dns_instructions": [
{ "type": "TXT", "name": "_drophere-connect.example.com", "value": "PUBLIC_TOKEN" },
{ "type": "CNAME", "name": "*.example.com", "value": "fallback.drophere.cc", "proxied": false }
]
}
Set up connected-domain DNS
Always copy the exact TXT token, record names, and wildcard target shown on the connected-domains page. The ownership token is public verification data, not a secret. These records do not change the root domain, nameservers, email records, or existing exact subdomains. Exact records such as www.example.com continue to override the wildcard.
Cloudflare
- Open the domain in Cloudflare, then go to DNS → Records.
- Add the displayed TXT record. Cloudflare normally accepts
_drophere-connectin the Name field and appends the root domain. Paste the displayed public token into Content and leave TTL on Auto. - Add the displayed CNAME. Use
*for Name and paste the exact Drophere target into Target. - Set the CNAME Proxy status to DNS only (grey cloud), leave TTL on Auto, and save. An orange-cloud Proxied record hides the required CNAME and Drophere will report a wildcard conflict.
- Return to the connected-domains page and select Refresh.
If a wildcard record already exists, do not add a duplicate or replace it blindly. Decide whether that existing wildcard can point to Drophere; otherwise keep using exact custom hostnames. Existing Cloudflare Tunnel records and other exact DNS records do not need to change.
Other DNS providers
Create the same TXT and wildcard CNAME using the exact values Drophere displays. Some providers want the short names _drophere-connect and *; others want the complete names shown in the instructions. Disable any proxy, CDN, or traffic-acceleration option for the wildcard so public DNS returns the CNAME directly. After saving both records, wait for DNS propagation and select Refresh. If the provider does not support wildcard CNAME records, register exact hostnames individually instead.
Use GET /api/v1/domain-connections to list connections, GET /api/v1/domain-connections/:domain for persisted status, and POST /api/v1/domain-connections/:domain/refresh after adding DNS. Refresh checks TXT, CNAME, A, and AAAA answers, persists the readback, and activates only after both proofs match. Pending claims do not reserve a registrable domain; activation is serialized and refuses another active authority or a fully serving, provider-bound direct child owned by another account. Pending, failed, unbound, and deeper exact rows do not block activation. Resolver failures preserve an already active DNS-proven authority.
Owned and shared roots return access_role plus sharing.mode and sharing.version. Owner responses also return the server-authoritative sharing.eligible_email_domain. Owners can call PATCH /api/v1/domain-connections/:domain with sharingMode set to private, selected, or email_domain, plus the last observed expectedSharingVersion. Selected sharing accepts up to 50 existing Drophere accounts at the owner's exact non-consumer email domain. Email-domain sharing makes any root owned by the connector available to every current or future verified Drophere account at the connector's exact non-consumer email domain. DNS proves the connector owns the shared root; Drophere account verification establishes each member's email. Agents inherit their authenticated user's permissions.
Before enabling email_domain, the human or agent must show the user the full current-and-future audience and obtain explicit confirmation. Send the exact latest sharing.eligible_email_domain as emailDomainConfirmation; Drophere rejects a missing or different value. Never derive it from browser storage, the connected root, or user input. The field is null when account-domain sharing is unavailable.
The connector remains provider, namespace, quota, and cleanup owner. Members see redacted connection data and can create and manage only their own exact children and routes to their own artifacts. The owner manages everything and takes control when editing a member route. Revocation blocks new and management writes without stopping existing sites. A stale sharing version returns 409 CONNECTED_DOMAIN_SHARING_CHANGED.
DELETE /api/v1/domain-connections/:domain disconnects only after every exact child is removed. External DNS is unchanged. Repeating DELETE after a lost success response returns already_disconnected: true.
Stable connection errors include CONNECTED_DOMAIN_NOT_FOUND, CONNECTED_DOMAIN_NOT_READY, CONNECTED_DOMAIN_LIMIT_REACHED, CONNECTED_DOMAIN_SHARING_CHANGED, CONNECTED_DOMAIN_SHARING_EMAIL_DOMAIN_CONFIRMATION_REQUIRED, CONNECTED_DOMAIN_AUTHORITY_EXPIRED, CONNECTED_DOMAIN_WILDCARD_CONFLICT, CONNECTED_DOMAIN_CHILD_DNS_CONFLICT, CONNECTED_DOMAIN_HAS_CHILDREN, CONNECTED_DOMAIN_OPERATION_IN_PROGRESS, CONNECTED_DOMAIN_RETRY_REQUIRED, CONNECTED_DOMAIN_DNS_ERROR, and CONNECTED_DOMAINS_NOT_CONFIGURED. Every error uses the JSON { "error", "code" } shape.
Add domain
Authenticated
curl -X POST https://drophere.cc/api/v1/domains -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "domain": "docs.example.com" }'
For a one-label child under an active connection, send { "domain": "docs", "connectedDomain": "example.com" }. Drophere rechecks the parent and requires the child to inherit the expected wildcard CNAME, then creates an exact Cloudflare custom hostname with HTTP validation. Provider completion is fenced on the active DNS-proven parent; a lost fence first claims the still-current local attempt, then performs provider-first cleanup. A superseded request cannot delete its successor's provider hostname. The response has registration_mode: "connected_subdomain", dns_managed_by_connection: true, and dns_instructions: null. Cloudflare makes wildcard custom hostnames Enterprise-only; on Drophere's current pay-as-you-go plan, unknown wildcard hostnames remain non-serving at the provider boundary before the request reaches Drophere.
Unlimited Pro allows 20 exact custom hostname rows across standalone domains and connected children. Pending, failed, uncertain, and member-created children count until deletion; shared children count against the connector. Admission is atomic with insert or ownership transfer. A provider-bound takeover reserves its destination slot before external cleanup, and that in-progress reservation is included in quota usage. Retries of an already-counted hostname stay available at the limit. A new hostname over the limit returns 409 CUSTOM_HOSTNAME_LIMIT_REACHED before provider creation.
{
"domain": "docs.example.com",
"status": "pending",
"ssl_status": "pending",
"provider_status": "pending",
"provider_ssl_status": "pending_validation",
"serving_ready": false,
"registration_mode": "standalone",
"connected_domain": null,
"connected_domain_label": null,
"dns_managed_by_connection": false,
"dns_instructions": {
"type": "CNAME",
"name": "docs.example.com",
"value": "fallback.drophere.cc",
"note": "Point docs.example.com to fallback.drophere.cc."
},
"ownership_verification": { "type": "txt", "name": "_cf-custom-hostname.docs.example.com", "value": "..." },
"ssl_validation_records": [],
"verification_errors": []
}
{
"domain": "alvaroiscool.example.com",
"status": "pending",
"ssl_status": "pending",
"provider_status": "pending",
"provider_ssl_status": "pending_validation",
"serving_ready": false,
"registration_mode": "connected_subdomain",
"connected_domain": "example.com",
"connected_domain_label": "alvaroiscool",
"dns_managed_by_connection": true,
"dns_instructions": null,
"ownership_verification": null,
"ssl_validation_records": [],
"verification_errors": []
}
Connected children request HTTP validation from Cloudflare, but ownership_verification is provider-returned diagnostic data and may be null, TXT-shaped, or HTTP-shaped. Treat registration_mode, dns_managed_by_connection, and dns_instructions as the stable Drophere discriminators.
Do not register *.drophere.cc names here. A request such as drophelloworld.drophere.cc returns 400 with code: "DROPHERE_HOSTNAME_RESERVED" and suggestedSlug: "drophelloworld"; create a persistent artifact with that slug instead.
New registrations reject Unicode and punycode IDNs. Read, refresh, detach, and delete continue to accept ASCII punycode hostnames for lifecycle compatibility with preexisting rows.
Provisioning: Drophere creates a Cloudflare for SaaS custom hostname and stores its ownership, DCV, hostname, and certificate state. When Cloudflare custom metadata is available, a local nonce is mirrored there and must match before Drophere adopts or deletes the provider record. Pay-as-you-go accounts without metadata allocation automatically use the exact Cloudflare hostname ID returned by the successful create and persisted on the registration. Later reads and deletes must match that stored ID and exact hostname; Drophere never adopts or deletes a metadata-less record discovered only by name. A short database lease rejects concurrent live claims while allowing a crashed claim to be retried. Its per-attempt token and captured applied-refresh generation prevent an expired request from overwriting either its successor or provider evidence applied after the lease began. A request timeout does not prove that Cloudflare cancelled the server-side create. Ambiguous outcomes remain provisioning_uncertain, retain their registration claim, and reject deletion even after the lease expires. A later retry claims the row first, then clears that uncertainty only after authoritative provider binding proof. A metadata-less create whose successful response is lost has no safe provider-ID proof and remains fail-closed for operator cleanup rather than being adopted by hostname. A verified connected-domain authority may convert a same-owner standalone child, reclaim an unbound foreign direct child, or reparent a child whose old authority is retired or expired. When a superseded standalone or connected child has a stored provider hostname ID, Drophere leases the exact old registration, proves the exact ID and hostname plus any available metadata, and deletes it provider-first. A fully serving foreign standalone child remains a conflict; a same-owner standalone child can be converted without losing its links. Only confirmed deletion or authoritative 404 advances the atomic reparent and new provider create; a timeout returns 502 CONNECTED_DOMAIN_CHILD_CLEANUP_RETRY_REQUIRED and preserves the old fail-closed registration for retry. If Cloudflare has a hostname bound to an exact superseded Drophere nonce, Drophere deletes it provider-first and retries with the new connected binding. Superseded cleanup uses an expiring, reclaimable lease. For same-owner replacement, the prior namespace and links stay durably fenced until the successor provider binding is persisted. If the new parent authority is lost after provider cleanup, Drophere clears the deleted binding and active statuses before returning; a same-owner standalone keeps its namespace and links in that fail-closed state. A late superseded writeback can clean only its proven provider record and converts the fallback to local-only identity, preserving that recovery path. Fully serving standalone foreign children remain conflicts. Traffic stays fail-closed until the bound provider record exists and both local and raw provider hostname/SSL statuses are active. Connected children also require their parent to remain active, TXT/wildcard verified, and free of a disconnect claim on every edge read. serving_ready applies that same parent-authority check in registration, list, get, and refresh responses, including the equivalent MCP tools. Apex domains require DNS-provider CNAME flattening or Cloudflare's separate apex-proxying product.
If an authoritative provider read explicitly reports that the Cloudflare zone has no fallback origin, authenticated registration and refresh set the account-level origin only to the environment-defined CF_CUSTOM_HOSTNAMES_CNAME_TARGET, then re-read the same exact provider ID. The repair target is never derived from the requested customer hostname.
For a foreign standalone child, authoritative provider hostname and SSL status—not stale local cached flags—decide whether it is serving. If both provider statuses are active, Drophere leaves the provider record and foreign links untouched and returns a registration conflict.
If the exact provider ID is created but the database binding fence changes before Drophere can persist it, Drophere deletes that exact provider record and returns 409 CUSTOM_DOMAIN_PROVISIONING_RETRY_REQUIRED with retryable: true. The same response is returned if authority changes while Drophere clears an exact provider ID already proven absent; that row remains provisioning_uncertain and immediately retryable. Retry the same registration request; do not change provider state directly.
List domains
Authenticated
curl https://drophere.cc/api/v1/domains -H "Authorization: Bearer YOUR_API_KEY"
{
"domains": [
{
"domain": "docs.example.com",
"status": "active",
"ssl_status": "active",
"serving_ready": true,
"created_at": "2026-03-11T10:00:00Z",
"links": [
{ "location": "", "slug": "bold-canvas" }
]
}
]
}
Get domain
Authenticated
Refresh domain status
Authenticated Reads Cloudflare state, verifies the provider binding proof, persists the readback, and returns the domain detail shape. Metadata-backed records require the registration nonce. Metadata-less records require the exact provider hostname ID already persisted on the registration and an exact hostname match. When no provider hostname ID is stored, hostname search is discovery only; Drophere reads the discovered exact ID again and requires matching custom metadata before persisting it. Refresh takes an exclusive two-minute claim with a monotonic generation and captures the registration's immutable namespace ID and binding nonce. Concurrent refresh and deletion attempts return 409 CUSTOM_DOMAIN_OPERATION_IN_PROGRESS. Every success, terminal failure, and transient-error writeback requires the exact refresh token and rejects a domain already claimed for deletion, so an expired refresh response cannot overwrite deletion state or a same-owner replacement registration. Refresh never rewrites cached link snapshots, so it cannot resurrect a concurrently deleted or retargeted link. Pending, unbound, or failed state removes stale routing KV and remains fail-closed. Refresh returns 409 CUSTOM_DOMAIN_OPERATION_IN_PROGRESS without contacting Cloudflare while a provisioning token exists or the row is provisioning_uncertain. Only POST /api/v1/domains can claim and reconcile an uncertain create after its lease expires.
Detach local domain registration
Authenticated Removes the domain registration, local links, and routing cache without creating, updating, or deleting any Cloudflare record. This recovery path is available for a failed foreign binding and for authoritative provider absence when no create outcome is uncertain. An expired provisioning_uncertain row is preserved when provider lookup is absent; retry POST /api/v1/domains so Drophere can reconcile the same registration binding. A hostname lookup is followed by an authoritative read of the exact record before a mismatch decision. Live provisioning and refresh claims return 409 CUSTOM_DOMAIN_OPERATION_IN_PROGRESS. If the provider record still has this registration's exact binding proof—custom metadata, or the persisted provider ID plus exact hostname—detach returns 409 CUSTOM_DOMAIN_PROVIDER_BINDING_MATCHES; use normal deletion so provider cleanup happens first. Provider errors leave local state intact for retry.
curl -X POST https://drophere.cc/api/v1/domains/docs.example.com/detach -H "Authorization: Bearer YOUR_API_KEY"
{
"success": true,
"local_only": true,
"provider_untouched": true
}
Delete domain
Authenticated
Claims the domain to block concurrent link writes and provider refreshes, verifies the Cloudflare binding proof, and deletes the custom hostname and certificates first. Metadata-backed records require the registration nonce; metadata-less records require the persisted provider ID plus exact hostname. When the row has no stored provider hostname ID, hostname search is discovery only: Drophere reads the discovered exact ID again and requires matching custom metadata before sending the delete request. A live refresh claim blocks deletion. Deletion returns 409 CUSTOM_DOMAIN_OPERATION_IN_PROGRESS while a live provisioning lease exists or while a create outcome remains provisioning_uncertain. Provider request timeouts do not imply server-side cancellation. After the lease expires, retry provisioning to reconcile the exact provider binding; deletion becomes available only after that uncertainty is cleared. One fenced database statement then removes local links and the domain row atomically, preventing orphan routes from reappearing after re-registration. Domain routing cache snapshots are bound to the registration's immutable namespace ID and provider nonce; legacy or mismatched snapshots fail closed. Provider failure releases the claim and leaves local ownership intact for retry. A binding mismatch marks local state failed and purges routing KV without deleting the foreign provider record. Repeated deletes succeed, and post-commit KV cleanup is best effort.
A legacy row without a verified provider binding returns provider_cleanup_skipped: true. This means only Drophere's local registration and routes were removed: Drophere did not prove or delete any provider hostname or certificates. An operator must verify and remove any remaining provider state. Clients must never describe this result as confirmed provider deletion.
A retry after the local row is already absent returns already_deleted: true. This is idempotent, but it cannot prove the outcome of an earlier provider operation. Clients must treat provider cleanup as unconfirmed and require an operator check.
curl -X DELETE https://drophere.cc/api/v1/domains/docs.example.com -H "Authorization: Bearer YOUR_API_KEY"
{
"success": true
}
{
"success": true,
"provider_cleanup_skipped": true
}
{
"success": true,
"already_deleted": true
}
Links
Links route URL paths on your handle or custom domain to specific artifacts. For example, alice.drophere.cc/blog can point to a different artifact than alice.drophere.cc/portfolio.
Create link
Authenticated
| Field | Type | Required | Description |
|---|---|---|---|
location | string | Yes | URL path segment (e.g., blog, docs). Use empty string "" for the bare root. __root__ is accepted as a compatibility alias and stored/returned as "". |
slug | string | Yes | Target artifact slug |
domain | string | No | Custom domain. If omitted, uses your handle. |
curl -X POST https://drophere.cc/api/v1/links -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "location": "blog", "slug": "quiet-river-b3m1" }'
{
"namespace": "alice",
"location": "blog",
"slug": "quiet-river-b3m1"
}
Creating a link with the same namespace + location upserts (updates the existing link).
Root link recipe: to serve an artifact at the bare handle URL, create a link with location: "":
curl -X POST https://drophere.cc/api/v1/links \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "location": "", "slug": "quiet-river-b3m1" }'
That makes https://alice.drophere.cc/ resolve to the artifact. Add a second link such as location: "docs" only when you also want https://alice.drophere.cc/docs.
For a custom-domain root, the canonical create/update and legacy __root__ cleanup run in one current-registration-locked database mutation. Cleanup requires the canonical write to match successfully, so stale cleanup cannot remove a root link from a replacement registration.
On a shared connected-domain child, members can list and mutate only routes they created, only while their current sharing grant remains valid, and only to artifacts they own. The connector manages every route and takes control when editing a member-created route. Revocation leaves existing routes serving until the connector removes them.
List links
Authenticated
curl https://drophere.cc/api/v1/links -H "Authorization: Bearer YOUR_API_KEY"
{
"links": [
{
"location": "",
"slug": "quiet-river-b3m1",
"namespace": "alice",
"namespaceType": "handle"
},
{
"location": "blog",
"slug": "quiet-river-b3m1",
"namespace": "alice",
"namespaceType": "handle"
}
]
}
Root links are listed with canonical location: "", never __root__.
Get link
Authenticated Use __root__ in the URL path when reading the root link because a path parameter cannot be empty. The response returns canonical location: "".
Update link
Authenticated Use PATCH /api/v1/links/__root__ to update the root link.
curl -X PATCH https://drophere.cc/api/v1/links/blog -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_API_KEY" -d '{ "slug": "new-blog-slug" }'
{
"success": true
}
Delete link
Authenticated Optional query param ?domain=example.com for domain-scoped links. Use DELETE /api/v1/links/__root__ to delete the root link.
curl -X DELETE https://drophere.cc/api/v1/links/blog -H "Authorization: Bearer YOUR_API_KEY"
{
"success": true
}
Key-Value Store
Per-artifact key-value storage, accessible from the artifact's own origin. No authentication required — designed for public read/write from hosted apps (e.g., game leaderboards, user preferences, shared state).
Store data belongs to the artifact itself, not its reusable slug. Deleting an artifact and later creating another with the same slug does not transfer the deleted artifact's keys. Artifacts created before this isolation was introduced retain access to their existing keys.
All store endpoints are served from the artifact's subdomain:
https://{slug}.drophere.cc/_api/store/
Also works via handles and custom domains.
Key validation
Keys must match: ^[a-zA-Z0-9._-:/]{1,480}$
Valid: score, game.level-1_data, leaderboard/level:1
Invalid: empty string, spaces, ../etc/passwd, unicode, keys > 480 chars
CORS
All store responses include CORS headers. OPTIONS requests return 204 with preflight support.
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, x-rate-limit-bypass
Get Value
No Auth Rate Limit 300 reads/min per IP per artifact.
curl https://bold-canvas.drophere.cc/_api/store/leaderboard
{
"value": [{ "name": "Alice", "score": 100 }],
"metadata": { "updatedAt": "2026-03-13T10:00:00Z" }
}
Errors
| Status | Code | Description |
|---|---|---|
404 | KEY_NOT_FOUND | Key does not exist |
429 | RATE_LIMITED | Rate limit exceeded |
Put Value
No Auth Rate Limit 30 writes/min per IP per artifact.
curl -X PUT https://bold-canvas.drophere.cc/_api/store/leaderboard -H "Content-Type: application/json" -d '[{ "name": "Alice", "score": 100 }]'
Body must be valid JSON, max 100 KB.
{
"ok": true,
"key": "leaderboard"
}
Errors
| Status | Code | Description |
|---|---|---|
400 | INVALID_KEY | Key fails validation |
400 | VALUE_TOO_LARGE | Body exceeds 100 KB |
400 | INVALID_JSON | Body is not valid JSON |
400 | INVALID_CONTENT_TYPE | Missing Content-Type: application/json |
429 | RATE_LIMITED | Rate limit exceeded |
Delete Value
No Auth Rate Limit 30 writes/min per IP per artifact.
curl -X DELETE https://bold-canvas.drophere.cc/_api/store/leaderboard
{
"ok": true
}
List Keys
No Auth Rate Limit 30 requests/min (write tier).
Optional query parameter: ?cursor=... for pagination.
curl https://bold-canvas.drophere.cc/_api/store
{
"keys": [
{
"name": "leaderboard",
"metadata": { "updatedAt": "2026-03-13T10:00:00Z" }
}
],
"cursor": null
}
Returns up to 1000 keys per page. If cursor is not null, pass it as ?cursor=... for the next page.
Service Variables
Encrypted server-side storage for API keys and secrets. Used by proxy routes to inject auth headers into upstream API calls. Values are encrypted at rest (AES-256-GCM) and never returned via the API.
- Max 50 variables per account
- Max 4 KB per value
- Names: alphanumeric + underscores, 1-64 chars (
/^[A-Za-z0-9_]{1,64}$/) - Optional
allowedUpstreamsrestricts which domains can receive the variable
Create or update variable
Authenticated
| Field | Type | Required | Description |
|---|---|---|---|
value | string | Yes | The secret value (max 4 KB) |
allowedUpstreams | string[] | No | Domain names this variable can be sent to |
curl -X PUT https://drophere.cc/api/v1/me/variables/OPENAI_KEY \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"value": "sk-abc123...",
"allowedUpstreams": ["api.openai.com"]
}'
{
"name": "OPENAI_KEY",
"allowedUpstreams": ["api.openai.com"],
"message": "Variable created"
}
allowedUpstreams is a security feature. When set, proxy routes will refuse to inject this variable into requests to non-matching upstream domains. This prevents accidental credential leakage if a manifest is misconfigured.
List variables
Authenticated
curl https://drophere.cc/api/v1/me/variables \
-H "Authorization: Bearer YOUR_API_KEY"
{
"variables": [
{
"name": "OPENAI_KEY",
"allowedUpstreams": ["api.openai.com"],
"createdAt": "2026-03-12T10:00:00Z",
"updatedAt": "2026-03-12T10:00:00Z"
}
]
}
Values are never included in the response. Only names and metadata are returned.
Delete variable
Authenticated
curl -X DELETE https://drophere.cc/api/v1/me/variables/OPENAI_KEY \
-H "Authorization: Bearer YOUR_API_KEY"
{
"name": "OPENAI_KEY",
"message": "Variable deleted"
}
Proxy Routes
Let your static sites call authenticated APIs without exposing credentials in client code. Deploy a .drophere/proxy.json manifest with your artifact, and the edge worker forwards requests from /_proxy/* paths to upstream APIs with injected auth headers.
Browser JS Edge Worker Upstream API
| | |
| fetch(/_proxy/api/chat) | |
| ───────────────────────────>| |
| | Load .drophere/proxy.json |
| | Decrypt ${OPENAI_KEY} |
| | |
| | fetch(upstream, {headers}) |
| | ───────────────────────────>|
| | <── response (streamed) |
| <── proxied response | |Manifest format
Include .drophere/proxy.json in your artifact's files:
{
"routes": {
"/api/chat": {
"upstream": "https://api.openai.com/v1/chat/completions",
"headers": { "Authorization": "Bearer ${OPENAI_KEY}" }
},
"/api/db/*": {
"upstream": "https://db.example.com/api",
"headers": { "apikey": "${DB_KEY}" },
"rateLimit": "20/hour/ip"
}
}
}
Route matching
- Exact match:
/api/chatmatches only/_proxy/api/chat - Wildcard:
/api/db/*matches/_proxy/api/db/anything— remaining path appended to upstream URL - Max 20 routes per manifest
Variable resolution
${VAR_NAME} references in headers are resolved from your service variables at request time. Variables are decrypted server-side — client code never sees credentials.
Security
upstreammust use HTTPS — HTTP is rejectedSet-Cookieheaders from upstream are stripped- Only
Content-TypeandAcceptheaders forwarded from client allowedUpstreamson variables enforced — variable rejected if upstream domain doesn't match- Max request body: 10 MB
Rate limiting
Default: 100 requests/hour/IP per route. Override per route with "rateLimit": "20/hour/ip". Format: {count}/{second|minute|hour}/ip.
Client usage
From your deployed static site's JavaScript:
// Call an authenticated API without exposing your key
const res = await fetch('/_proxy/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello' }]
})
});
const data = await res.json();
SSE streaming works transparently. If the upstream returns Content-Type: text/event-stream, the proxy streams the response without buffering — ideal for LLM chat interfaces.
MCP Server
drophere.cc speaks the Model Context Protocol natively. Point any MCP-capable client (Claude Desktop, Cursor, custom agents) at the server and it can publish, update, claim, password-protect, route, and delete artifacts — plus read and write the KV store and service variables — without you wiring up REST calls.
The MCP surface is a thin wrapper over the same authenticated REST API documented above. Anything an agent can do over REST it can do over MCP, with one deliberate exception: API-key rotation is not exposed, so a compromised agent can't lock you out of your own account.
Connect
Two equivalent transport forms, depending on whether your client can send custom headers:
Path-token form
API key embedded in the URL path. Useful for clients that don't expose a way to set Authorization headers (some agent UIs, simple HTTP MCP bridges). Treat the URL itself as the secret — anyone with it can act as you.
Bearer-header form
Standard MCP endpoint. Authenticate with Authorization: Bearer <apiKey>. Preferred form when your client supports it (key isn't logged in URLs / proxies / browser history).
Authenticated Same 64-hex API key you get from magic-link verification. Missing, malformed (not 64 hex chars), or revoked keys return 401.
Example: Claude Desktop config
{
"mcpServers": {
"drophere": {
"url": "https://drophere.cc/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
CORS
OPTIONS preflight is open (*). Allowed methods: GET, POST, OPTIONS. Allowed headers: Authorization, Content-Type, mcp-session-id. Preflight is cached 24 hours.
Tools
The server registers the tools below. Parameter schemas mirror the corresponding REST endpoints — each tool links to the relevant section for full request/response details.
MCP publishing
For small static/text artifacts, prefer drophere_publish_artifact. It accepts file content strings (path, contentText or content, contentType) and computes byte sizes internally, so clients do not need to pre-count bytes or base64 each file. drophere_create_static_site remains available as a compatibility alias. Successful one-shot responses include shareable: true, a canonical artifact summary, nextActions, entrypointUrl, hasIndexHtml, and htmlDetected.
Use drophere_create_artifact / drophere_update_artifact for large files, binary files, or incremental deploys. Their responses distinguish mcpUploads for drophere_upload_file (contentText for text or contentBase64 for bytes), directHttpUploads for raw HTTP PUTs, nextStep, siteUrlStatus, shareable, operationState, nextRecommendedAction, nextActions, and cleanup hints.
Use drophere_get_artifact to inspect pendingVersion.readyToFinalize and per-file upload status before publishing. After publish, use drophere_list_files for the live manifest and drophere_get_file to read back a deployed file. Publish uploaded pending versions with drophere_publish_uploaded_version; drophere_finalize_artifact remains available as the compatibility name. For review-first releases, use drophere_save_uploaded_version, then drophere_deploy_saved_version with the observed current version.
Status fields are intentionally distinct: status is the artifact row lifecycle, operationState is the current MCP workflow (pending_upload, ready_to_finalize, saved, or active), and siteUrlStatus is whether the public URL is live. Agents can use shareable as the simplest readiness signal.
For a bad pending version, update with corrected files or call drophere_discard_pending_version with the exact observed artifact slug and pending versionId. A stale version ID is rejected. If saved versions exist without a live version, discard preserves them and removes only the expected pending upload. Delete the whole artifact only when removal is intended.
Search & fetch
drophere_search— search your own artifacts by slug, viewer metadata title/description, or private tagsdrophere_fetch— fetch one owned artifact by slug; metadata includes viewer metadata and private tags
Artifacts — read
drophere_list_artifacts— see List Artifactsdrophere_get_artifact— see Get Artifactdrophere_list_artifact_versions— see Artifact Edit Grantsdrophere_list_files— list the current live version manifest, entrypoint URL, and HTML/index detection flagsdrophere_get_file— read one file from the current live version ascontentTextorcontentBase64drophere_get_artifact_access— see Get Access
Library
drophere_list_library_items,drophere_update_library_item,drophere_create_library_collection,drophere_list_library_collections,drophere_add_library_item_to_collection,drophere_suggest_library_routes,drophere_find_related_library_items— see Library
Artifact tags
drophere_get_artifact_tags,drophere_set_artifact_tags,drophere_list_tags— see Tags
Artifacts — write
drophere_publish_artifact— preferred path for small static/text artifacts; accepts content strings and computes sizes internallydrophere_create_static_site— compatibility alias fordrophere_publish_artifactdrophere_create_artifact— see Createdrophere_update_artifact— see Update (Incremental)drophere_publish_uploaded_version— publish a pending version after uploads completedrophere_save_uploaded_version— save an uploaded version without changing the live sitedrophere_deploy_saved_version— deploy or roll back to a saved version with optimistic concurrencydrophere_finalize_artifact— see Finalizedrophere_claim_artifact— see Claimdrophere_duplicate_artifact— see Duplicatedrophere_refresh_uploads— see Refresh Upload URLsdrophere_update_artifact_metadata— see Metadatadrophere_discard_pending_version— discard the exact expected pendingversionIdwithout deleting live or saved versionsdrophere_create_edit_grant,drophere_list_edit_grants,drophere_revoke_edit_grant— see Artifact Edit Grantsdrophere_set_artifact_access— see Set Visibilitydrophere_set_artifact_password/drophere_unset_artifact_password— see Password Protection
Collaboration
drophere_set_collaboration— see Collaborationdrophere_list_comments,drophere_add_comment,drophere_update_comment,drophere_delete_comment— see Collaboration
Upload
drophere_upload_file— wraps the file upload proxy. The tool takes a slug + version + path plus eithercontentTextfor UTF-8 text files orcontentBase64for exact bytes; do not send both. Large binary files can fall back to a capability URL.
Handles & links
drophere_set_handle,drophere_get_handle,drophere_delete_handle— see Handlesdrophere_set_link,drophere_get_link,drophere_list_links,drophere_delete_link— see Links
Custom domains
drophere_register_domain,drophere_list_domains,drophere_get_domain,drophere_refresh_domain,drophere_detach_domain,drophere_delete_domain— see Custom Domainsdrophere_connect_domain,drophere_list_connected_domains,drophere_get_connected_domain,drophere_refresh_connected_domain,drophere_disconnect_domain— connect one base for automatic exact subdomains
Service variables
drophere_set_variable,drophere_list_variables,drophere_delete_variable— see Service Variables
Key-Value store
drophere_kv_get,drophere_kv_set,drophere_kv_list,drophere_kv_delete— see Key-Value Store
Delete
drophere_delete_artifact— see Delete Artifact
Not exposed via MCP: POST /api/v1/me/api-key/rotate. Rotation is deliberately gated to direct API / web calls so an agent that misbehaves can't trade your existing key for a new one.
Rate Limits
MCP shares the same per-user buckets as the underlying REST endpoints — the wrapper doesn't add or subtract budget. In practice this means: 60 creates/hour, 5 key rotations/hour (REST-only), 30 writes/min and 300 reads/min on the KV store, 10 feedback submissions/hour. See Limits for the full table.
On top of those, the MCP transport itself applies a lightweight per-key throttle to absorb runaway agent loops — sustained traffic above a few requests/second will see 429. Back off and retry.
Slack
The @drophere Slack bot turns any HTML file you drop into Slack into a live URL. Mention the bot with an attachment, DM it, or use the Host on drophere message shortcut — it uploads the file, hosts it, and replies in-thread with the URL.
Slack-hosted artifacts are anonymous — they aren't linked to your drophere.cc account, even if you have one. By default they're restricted to your workspace's email domains (configured server-side); pass --public to drop the restriction.
Install
The bot is currently invite-only while we tune the workspace flow. Reach out via feedback with your Slack workspace name to request access.
Once installed, the bot exposes:
- The
@dropheremention in any channel it's invited to - Direct messages (DMs and group DMs)
- A message shortcut labelled Host on drophere
Usage
1. @-mention in a channel
Attach an HTML file and mention the bot:
@drophere here you go
[attach: report.html]
The bot replies in-thread:
Dropped! Your file is live:
https://bold-canvas.drophere.cc/
File: report.html
To make the artifact public (no domain restriction), add --public anywhere in the message:
@drophere ship this --public
[attach: landing.html]
2. DM the bot
Send an HTML attachment directly to @drophere in a DM or group DM. Same response. The --public flag works here too.
3. Message shortcut
On any Slack message that already has an HTML attachment, open the message actions menu (…) and pick Host on drophere. Useful for re-hosting a file a teammate posted earlier without needing to mention the bot.
The shortcut always hosts with the default workspace-domain restriction — there's no --public form. If you need a public URL, re-share with an @-mention instead.
Supported files: single HTML file per message (.html, .htm, or MIME text/html). Other file types are ignored. Size cap matches the anonymous upload limit.
Webhook Endpoints
For completeness — these are the URLs Slack itself calls into drophere.cc. They aren't part of the public API; you don't call them directly.
Slack Events API target. Handles url_verification (during app setup), app_mention, and DM/group-DM message events. Requests are authenticated via X-Slack-Signature (HMAC-SHA256 of the timestamp + raw body, keyed by the Slack signing secret) and deduplicated on X-Slack-Retry-Num.
Slack Interactivity target. Handles the drophere_host message shortcut (callback_id). Same signature scheme as /api/slack/events.
Content Serving
Artifacts are served at https://{slug}.drophere.cc/. The edge worker handles routing, access control, and file serving from R2 storage.
File serving priority
For a given request path against an artifact:
- Password gate — if password-protected, verify session cookie or show password form
- Access control — if restricted, verify email/session
- Proxy intercept —
/_proxy/*requests forwarded via proxy routes - Root path with
index.htmlin manifest — serve the HTML file - Root path with a single HTML file (not named
index.html) — serve it directly as the entry point - Exact file match in the version manifest
- Directory index: try
{path}/index.html - SPA fallback — if
spaModeenabled, serveindex.htmlfor unmatched paths - No match — 404
SPA Routing
When spaMode: true is set in viewer metadata, unmatched paths serve index.html instead of returning 404. This enables client-side routing for React, Vue, SvelteKit, and other SPA frameworks.
# Enable SPA mode
curl -X PATCH https://drophere.cc/api/v1/artifact/bold-canvas/metadata \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "viewerMetadata": { "spaMode": true } }'
- Static assets (JS, CSS, images) are still served normally — they match before the SPA fallback
/_api/*and/_proxy/*paths are never intercepted- Requires an
index.htmlin the artifact's root
Response headers
Files are served with:
Content-Type— from R2 metadata or file extension detectionCache-Control: public, max-age=3600, s-maxage=86400for public artifactsCache-Control: private, no-storefor restricted artifactsETag— from R2 object metadata
Visit Counter
Drop-in counter for any artifact. The script reads four metrics from a same-origin endpoint and writes the raw number into every matching element. No styling, no formatting — wrap and style however you like.
Usage
<p><span data-drophere-visits="total">—</span> visits</p>
<p><span data-drophere-visits="today">—</span> today</p>
<script src="https://drophere.cc/c/visits.js" defer></script>
The data-drophere-visits attribute selects which metric to inject. Any element type works — span, div, strong, etc. Multiple elements with the same metric are all filled from a single fetch.
Endpoint
Same-origin JSON endpoint. Resolves the artifact from the Host header. Inherits the artifact's password and access-control gates — restricted artifacts return 401/403 to non-allowed visitors.
Response
{
"total": 12483,
"today": 142,
"last7d": 1109,
"unique7d": 734
}
| Field | Description |
|---|---|
total | Lifetime visits across all time. |
today | Visits since 00:00 UTC today. |
last7d | Visits in the rolling 7-day window. |
unique7d | Approximate unique visitors in the last 7 days. Visitor identity is a daily-rotating IP+secret hash for privacy, so a returning visitor on different days is counted more than once. |
Cached at the edge for 30–60 seconds.
Programmatic access
Owner-only equivalent of the same-origin endpoint, for fetching counts from outside the artifact (e.g. a dashboard). Returns the same JSON shape.
curl -H "Authorization: Bearer $DROPHERE_API_KEY" \
https://drophere.cc/api/v1/artifact/bold-canvas/visits
Auto-Viewers
When an artifact has no index.html, drophere.cc renders a viewer based on the content type. This gives rich previews for images, PDFs, videos, and more.
| Content | Viewer | Features |
|---|---|---|
| Single image | Image viewer | Centered, responsive, dark background, og:image meta |
| Multiple images | Gallery | Lightbox with thumbnail grid and navigation |
| Single PDF | PDF viewer | Embedded PDF.js renderer |
| Single video | Video player | HTML5 <video> with controls |
| Single audio | Audio player | HTML5 <audio> with controls |
| Text, JSON, YAML, XML, JS, TS, TOML | Text viewer | Monospace, syntax-highlighted |
| Mixed files | Directory listing | File tree with icons, sizes, and download links |
Auto-viewers include og:title, og:description, and og:image meta tags when viewerMetadata is set.
Download as Markdown
Off by default. Agents can enable Download-as-Markdown per artifact at creation time (or later via a metadata update). When enabled, drophere runs the artifact's HTML through a converter and returns a .md file suitable for RAG pipelines, agent ingestion, and offline reading. This is best-effort.
Scope
Works on: user-uploaded HTML artifacts (served at {slug}.drophere.cc or a custom domain) where the agent has opted in (see below), including direct file paths and auto-viewer wrappers.
Does not apply to: the drophere marketing site (drophere.cc), developer docs (docs.drophere.cc), skill/install endpoints, API responses, the password gate, or non-HTML artifacts (images, PDFs, videos, JSON). Those are either not user content or not HTML, so the button is not injected and ?format=md does not apply.
Enabling the feature
The feature is gated by the viewer.markdownDownload flag in the artifact's viewer metadata. When it is true, the ?format=md endpoint and the floating .md button both become available. When it is missing or false, ?format=md returns 404 and no button is injected.
At creation time — pass viewer.markdownDownload: true in the Create Artifact body:
POST /api/v1/artifact
Content-Type: application/json
{
"files": [...],
"viewer": { "markdownDownload": true }
}
On an existing artifact — update the viewer metadata via PATCH (requires auth; must own the artifact):
PATCH /api/v1/artifact/{slug}/metadata
Authorization: Bearer <token>
Content-Type: application/json
{
"viewerMetadata": { "markdownDownload": true }
}
To turn off a previously-enabled artifact, send PATCH /metadata with { "viewerMetadata": { "markdownDownload": false } }.
How to trigger
Both mechanisms below only work when markdownDownload is enabled on the artifact. Otherwise the URL returns 404 and the button is not injected.
- Query parameter: append
?format=mdto any artifact URL. - Floating button: HTML responses from MD-enabled artifacts are injected with a small
.mdbutton in the bottom-right corner that clicks through to the same URL with?format=md. - Alternate link: MD-enabled HTML responses also include
<link rel="alternate" type="text/markdown" href="?format=md">in<head>for NO-JS and CSP-strict clients.
# Download the rendered index.html as Markdown (artifact must have markdownDownload: true)
curl -L "https://bold-canvas.drophere.cc/?format=md" > page.md
# Works for any HTML file path on an MD-enabled artifact
curl -L "https://bold-canvas.drophere.cc/docs/guide.html?format=md" > guide.md
The button is only injected on top-level navigations (Sec-Fetch-Dest: document or absent). Iframe, object, and embed fetches never get the button.
Response
Content-Type: text/markdown; charset=utf-8Content-Disposition: attachment; filename="{slug}.md"Cache-Control: public, max-age=3600, s-maxage=86400for public artifacts;private, no-storefor password-protected or access-controlled artifacts.
If the source file is already Markdown (.md, .markdown, or text/markdown), it is served verbatim with attachment headers — no conversion.
Limits
- Size cap: 2 MB of HTML input. Larger files return
413 Payload Too Large. - Content type: only
text/htmlsources are converted. Files withtext/markdownor a.md/.markdownextension are served verbatim as attachments (no conversion). All other content types return415 Unsupported Media Type. - Rate limit: 30 conversions per IP per minute per slug. Excess requests get
429with aRetry-Afterheader. - Auth inheritance:
?format=mdrespects the same password, expiry, and access-control gates as the underlying page. Restricted artifacts require the same auth as the HTML version.
Error codes
| Code | Meaning |
|---|---|
404 | Artifact does not have markdownDownload enabled, or source file does not exist. |
413 | HTML exceeds the 2 MB conversion cap. |
415 | Source is not HTML or Markdown (e.g. an image or binary file). |
429 | Rate limit exceeded (30/min per IP per slug). |
Limits
Upload size limits
| Per file | Per artifact (total) | |
|---|---|---|
| Anonymous | 100 MB | 250 MB |
| Authenticated | 1 GB | 5 GB |
Exceeding a limit returns 413 with error, details, and limits fields.
File count limits
| Max files per artifact | |
|---|---|
| Anonymous | 100 |
| Authenticated | 500 |
Rate limits
| Operation | Limit | Window | Scope |
|---|---|---|---|
| Create artifact (anonymous) | 5 | 1 hour | Per IP |
| Create artifact (authenticated) | 60 | 1 hour | Per user |
| Request auth code | 1 | 60 seconds | Per email |
| Request visitor code | 1 | 60 seconds | Per email + slug |
| Submit feedback | 10 | 1 hour | Per IP |
| Store reads | 300 | 1 minute | Per IP per artifact |
| Store writes | 30 | 1 minute | Per IP per artifact |
| Proxy routes (default) | 100 | 1 hour | Per IP per route |
| Password attempts | 10 | 1 minute | Per IP per slug |
Rate-limited requests return 429.
TTL and expiry
| Resource | TTL |
|---|---|
| Anonymous artifacts | 24 hours (fixed) |
| Authenticated artifacts | Indefinite (or custom ttlSeconds) |
| Upload URLs | 10 minutes |
| Auth codes | 15 minutes |
| Visitor codes | 15 minutes |
| Visitor sessions | 30 days |
| Password sessions | 30 days |
| Store values | 1 year |
| Idempotency keys | 24 hours |
Errors
Errors always include a human-readable error string. Feature-specific errors may also include a stable machine-readable code and additional details.
{
"error": "Human-readable error message",
"code": "OPTIONAL_STABLE_MACHINE_READABLE_CODE",
"details": "Optional additional context"
}
Status codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
400 | Bad request — invalid input or validation error |
401 | Authentication required or API key invalid |
403 | Forbidden — authenticated but not the owner or invalid claim token |
404 | Resource not found |
409 | Conflict — duplicate slug, handle taken, version mismatch |
410 | Gone — artifact has expired |
413 | Payload too large — file or artifact size limit exceeded |
429 | Rate limit exceeded |
503 | Service temporarily unavailable |
Feedback
Submit feedback about drophere.cc. No authentication required.
No Auth Rate Limit 10 per hour per IP.
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | 1-2000 characters |
slug | string | No | Related artifact slug (max 100 chars) |
source | string | No | Where feedback came from (e.g., skill, api) |
curl -X POST https://drophere.cc/api/v1/feedback -H "Content-Type: application/json" -d '{
"message": "Upload URL expired before upload finished",
"slug": "bold-canvas",
"source": "skill"
}'
{
"received": true
}
Capability Discovery
Agents can discover available API capabilities without reinstalling the skill. The endpoint returns a compact index of feature groups, related endpoints, and links to the Markdown and HTML docs.
No Auth
curl https://drophere.cc/api/v1/skill/docs
{
"version": "0.4.0",
"capabilities": [
{
"name": "publish",
"summary": "Upload static files to the web instantly",
"endpoints": ["..."]
},
{
"name": "vanity-artifact-urls",
"summary": "Paid persistent artifacts can request a custom artifact subdomain at creation time",
"endpoints": ["POST /api/v1/artifact"],
"tools": ["drophere_publish_artifact", "drophere_create_static_site", "drophere_create_artifact"]
},
{
"name": "collaboration",
"summary": "Enable anchored comments, replies, moderation, and attachments",
"endpoints": ["..."]
},
{
"name": "artifact-tags",
"summary": "Private artifact-level tags for knowledge discovery and agent search",
"endpoints": ["..."],
"tools": ["drophere_get_artifact_tags", "drophere_set_artifact_tags", "drophere_list_tags"]
},
{
"name": "mcp",
"summary": "Model Context Protocol wrapper over the REST/API and artifact store surfaces",
"endpoints": ["..."],
"tools": ["drophere_publish_artifact", "drophere_upload_file", "drophere_list_files", "drophere_get_file", "drophere_list_tags", "drophere_publish_uploaded_version", "drophere_save_uploaded_version", "drophere_deploy_saved_version"]
}
],
"docsUrl": "https://drophere.cc/skill/references/API.md",
"markdownDocsUrl": "https://drophere.cc/skill/references/API.md",
"htmlDocsUrl": "https://docs.drophere.cc/"
}
Cached for 1 hour (Cache-Control: public, max-age=3600).
Health Check
No Auth
curl https://drophere.cc/api/health
{
"status": "ok",
"timestamp": "2026-03-12T10:00:00Z"
}
drophere.cc — Instant static hosting for AI agents.