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" }
    ]
  }'
Response
{
  "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"
  }'
Response
{
  "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

POST /api/auth/agent/request-code
curl -X POST https://drophere.cc/api/auth/agent/request-code   -H "Content-Type: application/json"   -d '{ "email": "you@example.com" }'
Response 200
{
  "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

POST /api/auth/agent/verify-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.

Response 200
{
  "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.

POST /api/auth/browser/session auth: required
curl -X POST https://drophere.cc/api/auth/browser/session   -H "Authorization: Bearer <api_key>"   -H "Origin: https://drophere.cc"
Response 200
{
  "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:

  1. --api-key flag
  2. DROPHERE_API_KEY environment variable
  3. 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:

POST /api/v1/me/api-key/rotate auth: required
curl -X POST https://drophere.cc/api/v1/me/api-key/rotate   -H "Authorization: Bearer <current_key>"
Response 200
{
  "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-codeverify-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.

Plans

GET /api/v1/billing/plans

No Auth

Response 200
{
  "plans": [
    { "id": "unlimited", "price": "$4.99/month" },
    { "id": "secure", "price": "$9.99/month" }
  ]
}

Status

GET /api/v1/billing/status auth: required
Response 200
{
  "plan": "free_token",
  "usage": { "persistentArtifacts": 3, "persistentArtifactLimit": 10 },
  "features": { "apiAndMcp": true, "secureAccessControls": false },
  "upgradeOptions": [{ "plan": "unlimited", "checkoutEndpoint": "/api/v1/billing/checkout" }]
}

Checkout

POST /api/v1/billing/checkout auth: required
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

POST /api/v1/billing/portal auth: required

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.

POST /api/v1/machine/artifact

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"
}
Unpaid response 402
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..."
}
Paid response 201
{
  "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.

POST /api/v1/machine/artifact/:slug/finalize

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.

POST /api/v1/machine/artifact/:slug/uploads/refresh

Reissue upload URLs for missing pending files while the machine upload window is still valid. Requires X-Drophere-Machine-Token.

DELETE /api/v1/machine/artifact/:slug

Delete a paid machine artifact. Requires X-Drophere-Machine-Token.

Create Artifact

Start a new artifact upload. Send a file manifest and receive upload URLs.

POST /api/v1/artifact

Auth Optional Anonymous uploads (no auth) get a 24-hour TTL and a claimToken.

Request body

FieldTypeRequiredDescription
filesFileManifest[]YesArray of file entries. Must be non-empty.
files[].pathstringYesRelative file path. Must not contain ..
files[].sizenumberYesExact file size in bytes
files[].contentTypestringYesMIME type (e.g., text/html)
files[].hashstringNoSHA-256 hash for incremental deploys
ttlSecondsnumberNoCustom TTL (authenticated only). Anonymous is always 24h.
slugstringNoPaid vanity artifact URL slug. Authenticated persistent artifacts only. Lowercase letters, numbers, and hyphens, 2-63 chars, no leading/trailing hyphen.
langstringNoLanguage for generated slug words: en (default) or es. Always validated (invalid values return 400 INVALID_LANG) but has no effect when slug is provided.
viewerobjectNoOptional {title?, description?, ogImagePath?, spaMode?, markdownDownload?} for auto-viewer and artifact behavior
sourcestringNoOrigin 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"). A numeric suffix (pulpo-bailarin-7) is appended only when the bare pair is taken, growing 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"
  }'
Response 201
{
  "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"
}

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

StatusCodeError
400Invalid file manifest (missing fields, .. in path, empty array)
400CUSTOM_SLUG_INVALIDslug is not a valid lowercase DNS label or is reserved
400CUSTOM_SLUG_REQUIRES_PERSISTENT_ARTIFACTslug was combined with ttlSeconds; vanity slugs are persistent only
400INVALID_LANGlang was not one of the supported values (en, es)
402ACCOUNT_REQUIREDA signed-in account is required before claiming a vanity artifact slug
402PLAN_REQUIREDThe signed-in account does not have custom_artifact_slugs
409CUSTOM_SLUG_UNAVAILABLEThe slug is already used by an artifact, retained reservation, or handle
413File or total artifact size exceeds limit
429Rate limit exceeded

Upload Files

Upload each file to the URL returned in the uploads array. The API streams the body to R2 storage.

PUT /api/v1/upload/:slug/:versionId/:filePath
curl -X PUT "UPLOAD_URL_FROM_CREATE"   -H "Content-Type: text/html"   --data-binary @index.html
Response 200
{
  "ok": true,
  "path": "index.html"
}

Upload URLs are time-limited. Complete all uploads within 10 minutes of creating the artifact.

Finalize Artifact

Mark an upload as complete. This activates the artifact and makes it accessible at its URL. For incremental deploys, unchanged files are copied server-side during finalization.

POST /api/v1/artifact/:slug/finalize

Authenticated Requires Bearer token, claimToken in body for anonymous artifacts, or an edit grant token via X-Drophere-Edit-Token.

Request body

FieldTypeRequiredDescription
versionIdstringYesMust match the versionId from create/update
claimTokenstringNoRequired for anonymous artifacts
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" }'
Response 200
{
  "slug": "bold-canvas",
  "versionId": "550e8400-e29b-41d4-a716-446655440000",
  "siteUrl": "https://bold-canvas.drophere.cc/",
  "expiresAt": null,
  "feedback": "https://drophere.cc/api/v1/feedback"
}

Errors

StatusError
400versionId is required
401Authentication required
403Invalid or missing claim token / not the artifact owner
404Artifact or version not found
409versionId 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.

PUT /api/v1/artifact/:slug

Authenticated Requires Bearer token, claimToken in body, or an edit grant token via X-Drophere-Edit-Token.

Request body

FieldTypeRequiredDescription
filesFileManifest[]YesComplete manifest for owners/claim tokens; changed or new paths for edit-token merge updates
claimTokenstringNoRequired for anonymous artifacts
baseVersionIdstringNoRequired for edit grant updates; must match the current live version
deletePathsstring[]NoEdit tokens only. Explicit paths to remove; omission never deletes files
manifestModemerge | replaceNoEdit tokens only. Defaults to safe merge; replace opts into a complete-manifest update
summarystringNoOptional 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..." }
    ]
  }'
Response 200
{
  "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.

Errors

StatusError
403Invalid claim token or not the artifact owner
404Artifact not found
409Base version changed; recreate update from current version
410Artifact has expired
413File 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

POST /api/v1/artifact/:slug/edit-grants

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 }'
Response 201
{
  "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

GET /api/v1/artifact/:slug/edit-grants?includeRevoked=false

Authenticated Artifact owner Bearer token. Token values are never returned.

Revoke Edit Grant

DELETE /api/v1/artifact/:slug/edit-grants/:grantId

Authenticated Artifact owner Bearer token. Revocation is idempotent.

Edit Context and Raw Source

GET /api/v1/artifact/:slug/edit-context?include=manifest

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.

GET /api/v1/artifact/:slug/source/:path

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.

GET /api/v1/artifact/:slug/source-search?query=heading&path=index.html

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

POST /api/v1/artifact/:slug/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.

StatusEditor errors
400Invalid search, context, source path, line range, edit request, or operation
401EDIT_TOKEN_REQUIRED
403Missing scope, deploy capability, or owner mismatch
404Artifact, manifest file, or source object not found
409Inactive artifact, stale base, existing pending version, changed state, or exact-match conflict
410ARTIFACT_EXPIRED
413Non-text, per-file, request, aggregate output, or artifact size limit
416INVALID_BYTE_RANGE
429EDIT_RATE_LIMITED
500Missing current version or version creation failure
502EDIT_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

GET /api/v1/artifact/:slug/versions?limit=50

Authenticated Artifact owner Bearer token. Lists immutable version snapshots with base version, actor attribution, edit-grant label, summary, file count, and finalized timestamp. This is history, not rollback; restoring old versions remains owner-only and outside edit grants.

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

GET /api/v1/me/features

Authenticated Bearer token or Drophere account session.

{ "features": { "htmlQuickEdit": true } }

Preview Text Edit

POST /api/v1/artifact/:slug/quick-edits/preview

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

POST /api/v1/artifact/:slug/quick-edits/publish

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.

Response 200
{
  "slug": "bold-canvas",
  "versionId": "new-version-id",
  "baseVersionId": "current-version-id",
  "siteUrl": "https://bold-canvas.drophere.cc/",
  "summary": "Quick edit: update visible text"
}

Claim Artifact

Transfer an anonymous artifact to your authenticated account. This removes the 24-hour expiry and the claim token, making the artifact permanent.

POST /api/v1/artifact/:slug/claim

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" }'
Response 200
{
  "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.

GET /api/v1/artifact/:slug

Authenticated Must own the artifact.

curl https://drophere.cc/api/v1/artifact/bold-canvas   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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.

GET /api/v1/artifacts

Authenticated

curl https://drophere.cc/api/v1/artifacts   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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.

GET /api/v1/library/items

Authenticated

Query params: q, collectionId, tag, source, status, visibility, favorite=true, routed=true, archived=true, limit, cursor.

Response 200
{
  "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
}
PATCH /api/v1/library/items/:artifactSlug

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.

PATCH /api/v1/artifact/:slug/metadata

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"
    }
  }'
Response 200
{
  "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

GET /api/v1/artifact/:slug/tags

Authenticated Must own the artifact.

curl https://drophere.cc/api/v1/artifact/bold-canvas/tags   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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

PATCH /api/v1/artifact/:slug/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.

Response 200
{
  "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

GET /api/v1/tags

Authenticated

curl https://drophere.cc/api/v1/tags   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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.

DELETE /api/v1/artifact/:slug

Authenticated Must own the artifact.

curl -X DELETE https://drophere.cc/api/v1/artifact/bold-canvas   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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

PATCH /api/v1/artifact/:slug/password

Authenticated Must own the artifact.

Request body

FieldTypeRequiredDescription
passwordstring or nullYes8-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" }'
Response 200
{
  "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

PATCH /api/v1/artifact/:slug/collaboration

Authenticated Must own the artifact.

FieldTypeRequiredDescription
enabledbooleanYesEnable or hide the collaboration layer.
commentPolicystringNoauthenticated, anyone, same_domain, or specific_accounts.
commentDomainstring or nullNoRequired for same_domain unless inferred from the owner's non-consumer email domain.
commentAllowedEmailsstring[] or nullNoRequired 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 }'
Response 200
{
  "slug": "bold-canvas",
  "collaborationEnabled": true,
  "commentPolicy": "authenticated",
  "commentDomain": null,
  "commentAllowedEmails": null
}

Set view and comment permissions atomically

PATCH /api/v1/artifact/:slug/permissions

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"]
    }
  }'
Response 200
{
  "slug": "bold-canvas",
  "access": {
    "visibility": "public",
    "allowedEmails": null,
    "allowedDomains": null
  },
  "collaboration": {
    "enabled": true,
    "commentPolicy": "specific_accounts",
    "commentDomain": null,
    "commentAllowedEmails": ["alice@example.com"]
  }
}

Read comments

GET /api/v1/artifact/:slug/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

POST /api/v1/artifact/:slug/comments
POST /api/v1/artifact/:slug/comments/:threadId/reply
PATCH /api/v1/artifact/:slug/comments/:threadId
DELETE /api/v1/artifact/:slug/comments/:threadId
DELETE /api/v1/artifact/:slug/comments/:threadId/messages/:messageId
POST /api/v1/artifact/:slug/comments/attachments
GET /api/v1/artifact/:slug/comments/attachments/:attachmentId
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.

POST /api/v1/artifact/:slug/duplicate

Authenticated Must own the source artifact.

curl -X POST https://drophere.cc/api/v1/artifact/bold-canvas/duplicate \
  -H "Authorization: Bearer YOUR_API_KEY"
Response 201
{
  "slug": "calm-reef-x9z1",
  "sourceSlug": "bold-canvas",
  "versionId": "550e8400-e29b-41d4-a716-446655440000",
  "siteUrl": "https://calm-reef-x9z1.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.

POST /api/v1/artifact/:slug/uploads/refresh

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"
Response 200
{
  "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
}

Access Control

Restrict who can view an artifact by email address or email domain. By default, all artifacts are public.

Set Visibility

PATCH /api/v1/artifact/:slug/access

Authenticated Must own the artifact.

Request body

FieldTypeRequiredDescription
visibilitystringYes"public" or "restricted"
allowedEmailsstring[]NoUp to 100 email addresses
allowedDomainsstring[]NoUp 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"]
  }'
Response 200
{
  "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

StatusError
400Invalid visibility, email, or domain format
400Consumer domain blocked
400At least one email or domain required for restricted
403Not the artifact owner
404Artifact not found
410Artifact has expired

Get Access Control

GET /api/v1/artifact/:slug/access

Authenticated Must own the artifact.

curl https://drophere.cc/api/v1/artifact/bold-canvas/access   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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

POST /api/v1/visitor/request-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" }'
Response 200
{
  "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

POST /api/v1/visitor/verify-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" }'
Response 200
{
  "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.

PatternResolution
{slug}.drophere.ccDirect artifact access
{handle}.drophere.ccHandle 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.

FeatureURL shapePurposeLimits
Vanity artifact slughttps://client-demo.drophere.cc/One artifact gets the root subdomain directlyPaid persistent artifact, chosen only at creation time
Handlehttps://acme.drophere.cc/docsAccount namespace that routes paths to one or more artifactsOne handle per account

Decision rule for agents:

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

POST /api/v1/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" }'
Response 201
{
  "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

StatusCodeError
400HANDLE_INVALIDInvalid handle format
409HANDLE_ALREADY_SETThe account already has one handle. The response includes the current handle, hostname, and nextAction pointing single-site root URL requests to artifact slug.
409HANDLE_UNAVAILABLEThe handle name is already used by a handle, artifact slug, or retained vanity reservation

Get handle

GET /api/v1/handle

Authenticated

curl https://drophere.cc/api/v1/handle   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "handle": "alice",
  "hostname": "alice.drophere.cc",
  "namespace_id": "user-uuid",
  "links": [
    { "location": "", "slug": "bold-canvas" },
    { "location": "blog", "slug": "quiet-river-b3m1" }
  ]
}

Change handle

PATCH /api/v1/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" }'
Response 200
{
  "handle": "new-name",
  "hostname": "new-name.drophere.cc"
}

Release handle

DELETE /api/v1/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"
Response 200
{
  "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.

Add domain

POST /api/v1/domains

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" }'
Response 201
{
  "domain": "docs.example.com",
  "status": "pending",
  "dns_instructions": {
    "type": "CNAME",
    "name": "docs.example.com",
    "value": "fallback.drophere.cc",
    "note": "Add a CNAME record pointing docs.example.com to fallback.drophere.cc. If this is an apex domain, use an ALIAS record instead."
  }
}

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.

DNS setup: Add a CNAME record pointing your domain to fallback.drophere.cc. For apex domains (e.g., example.com), use an ALIAS record if your DNS provider supports it.

List domains

GET /api/v1/domains

Authenticated

curl https://drophere.cc/api/v1/domains   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "domains": [
    {
      "domain": "docs.example.com",
      "status": "active",
      "ssl_status": "active",
      "created_at": "2026-03-11T10:00:00Z",
      "links": [
        { "location": "", "slug": "bold-canvas" }
      ]
    }
  ]
}

Get domain

GET /api/v1/domains/:domain

Authenticated

Delete domain

DELETE /api/v1/domains/:domain

Authenticated

Removes the domain and all its associated links. KV entries are cleaned up.

curl -X DELETE https://drophere.cc/api/v1/domains/docs.example.com   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "success": true
}

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

POST /api/v1/links

Authenticated

FieldTypeRequiredDescription
locationstringYesURL path segment (e.g., blog, docs). Use empty string "" for the bare root. __root__ is accepted as a compatibility alias and stored/returned as "".
slugstringYesTarget artifact slug
domainstringNoCustom 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" }'
Response 201
{
  "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.

List links

GET /api/v1/links

Authenticated

curl https://drophere.cc/api/v1/links   -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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

GET /api/v1/links/:location

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

PATCH /api/v1/links/:location

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" }'
Response 200
{
  "success": true
}

Delete link

DELETE /api/v1/links/:location

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"
Response 200
{
  "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).

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

GET /_api/store/:key

No Auth Rate Limit 300 reads/min per IP per artifact.

curl https://bold-canvas.drophere.cc/_api/store/leaderboard
Response 200
{
  "value": [{ "name": "Alice", "score": 100 }],
  "metadata": { "updatedAt": "2026-03-13T10:00:00Z" }
}

Errors

StatusCodeDescription
404KEY_NOT_FOUNDKey does not exist
429RATE_LIMITEDRate limit exceeded

Put Value

PUT /_api/store/:key

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.

Response 200
{
  "ok": true,
  "key": "leaderboard"
}

Errors

StatusCodeDescription
400INVALID_KEYKey fails validation
400VALUE_TOO_LARGEBody exceeds 100 KB
400INVALID_JSONBody is not valid JSON
400INVALID_CONTENT_TYPEMissing Content-Type: application/json
429RATE_LIMITEDRate limit exceeded

Delete Value

DELETE /_api/store/:key

No Auth Rate Limit 30 writes/min per IP per artifact.

curl -X DELETE https://bold-canvas.drophere.cc/_api/store/leaderboard
Response 200
{
  "ok": true
}

List Keys

GET /_api/store

No Auth Rate Limit 30 requests/min (write tier).

Optional query parameter: ?cursor=... for pagination.

curl https://bold-canvas.drophere.cc/_api/store
Response 200
{
  "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.

Create or update variable

PUT /api/v1/me/variables/:name

Authenticated

FieldTypeRequiredDescription
valuestringYesThe secret value (max 4 KB)
allowedUpstreamsstring[]NoDomain 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"]
  }'
Response 201 (created) / 200 (updated)
{
  "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

GET /api/v1/me/variables

Authenticated

curl https://drophere.cc/api/v1/me/variables \
  -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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

DELETE /api/v1/me/variables/:name

Authenticated

curl -X DELETE https://drophere.cc/api/v1/me/variables/OPENAI_KEY \
  -H "Authorization: Bearer YOUR_API_KEY"
Response 200
{
  "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

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

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

https://drophere.cc/mcp/<apiKey>

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

https://drophere.cc/mcp

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.

Status fields are intentionally distinct: status is the artifact row lifecycle, operationState is the current MCP workflow (pending_upload, ready_to_finalize, 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 use drophere_discard_pending_version instead of deleting the whole artifact unless removal is intended.

Search & fetch

Artifacts — read

Library

Artifact tags

Artifacts — write

Collaboration

Upload

Handles & links

Service variables

Key-Value store

Delete

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:

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.

POST/api/slack/events

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.

POST/api/slack/interact

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:

  1. Password gate — if password-protected, verify session cookie or show password form
  2. Access control — if restricted, verify email/session
  3. Proxy intercept/_proxy/* requests forwarded via proxy routes
  4. Root path with index.html in manifest — serve the HTML file
  5. Root path with a single HTML file (not named index.html) — serve it directly as the entry point
  6. Exact file match in the version manifest
  7. Directory index: try {path}/index.html
  8. SPA fallback — if spaMode enabled, serve index.html for unmatched paths
  9. 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 } }'

Response headers

Files are served with:

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

GET/_drophere/visits

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
}
FieldDescription
totalLifetime visits across all time.
todayVisits since 00:00 UTC today.
last7dVisits in the rolling 7-day window.
unique7dApproximate 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

GET/api/v1/artifact/:slug/visitsAuth

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.

ContentViewerFeatures
Single imageImage viewerCentered, responsive, dark background, og:image meta
Multiple imagesGalleryLightbox with thumbnail grid and navigation
Single PDFPDF viewerEmbedded PDF.js renderer
Single videoVideo playerHTML5 <video> with controls
Single audioAudio playerHTML5 <audio> with controls
Text, JSON, YAML, XML, JS, TS, TOMLText viewerMonospace, syntax-highlighted
Mixed filesDirectory listingFile 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.

# 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

If the source file is already Markdown (.md, .markdown, or text/markdown), it is served verbatim with attachment headers — no conversion.

Limits

Error codes

CodeMeaning
404Artifact does not have markdownDownload enabled, or source file does not exist.
413HTML exceeds the 2 MB conversion cap.
415Source is not HTML or Markdown (e.g. an image or binary file).
429Rate limit exceeded (30/min per IP per slug).

Limits

Upload size limits

Per filePer artifact (total)
Anonymous100 MB250 MB
Authenticated1 GB5 GB

Exceeding a limit returns 413 with error, details, and limits fields.

File count limits

Max files per artifact
Anonymous100
Authenticated500

Rate limits

OperationLimitWindowScope
Create artifact (anonymous)51 hourPer IP
Create artifact (authenticated)601 hourPer user
Request auth code160 secondsPer email
Request visitor code160 secondsPer email + slug
Submit feedback101 hourPer IP
Store reads3001 minutePer IP per artifact
Store writes301 minutePer IP per artifact
Proxy routes (default)1001 hourPer IP per route
Password attempts101 minutePer IP per slug

Rate-limited requests return 429.

TTL and expiry

ResourceTTL
Anonymous artifacts24 hours (fixed)
Authenticated artifactsIndefinite (or custom ttlSeconds)
Upload URLs10 minutes
Auth codes15 minutes
Visitor codes15 minutes
Visitor sessions30 days
Password sessions30 days
Store values1 year
Idempotency keys24 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

CodeMeaning
200Success
201Resource created
400Bad request — invalid input or validation error
401Authentication required or API key invalid
403Forbidden — authenticated but not the owner or invalid claim token
404Resource not found
409Conflict — duplicate slug, handle taken, version mismatch
410Gone — artifact has expired
413Payload too large — file or artifact size limit exceeded
429Rate limit exceeded
503Service temporarily unavailable

Feedback

Submit feedback about drophere.cc. No authentication required.

POST /api/v1/feedback

No Auth Rate Limit 10 per hour per IP.

FieldTypeRequiredDescription
messagestringYes1-2000 characters
slugstringNoRelated artifact slug (max 100 chars)
sourcestringNoWhere 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"
  }'
Response 201
{
  "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.

GET /api/v1/skill/docs

No Auth

curl https://drophere.cc/api/v1/skill/docs
Response 200
{
  "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"]
    }
  ],
  "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

GET /api/health

No Auth

curl https://drophere.cc/api/health
Response 200
{
  "status": "ok",
  "timestamp": "2026-03-12T10:00:00Z"
}

drophere.cc — Instant static hosting for AI agents.