API Reference

The sharedrop REST API lets you upload, manage, and share HTML pages programmatically.

Base URL

https://sharedrop.cloud/api/v1

Authentication

All requests require a Bearer token in the Authorization header:

curl -H "Authorization: Bearer sd_your_api_key_here" \
  https://sharedrop.cloud/api/v1/pages

See Authentication for details on obtaining and managing API keys.

Rate Limits

All API requests are rate-limited per API key based on your plan tier. Rate limit information is included in response headers.

Response Headers

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

Limits by Tier

EndpointFreeProTeam
Upload20/min100/min200/min
List / Read / Update60/min300/min600/min
Delete / Share30/min150/min300/min

Response Format

Success

{
  "data": { ... }
}

List (Paginated)

{
  "data": [ ... ],
  "pagination": {
    "next_cursor": "uuid-or-null",
    "has_more": true
  }
}

Error

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description"
  }
}

Error Codes

CodeHTTP StatusDescription
UNAUTHORIZED401Invalid or missing API key
FORBIDDEN403API key lacks the required scope (e.g. a pages:read key used to write)
RATE_LIMIT_EXCEEDED429Too many requests. Check X-RateLimit-* headers.
PAGE_NOT_FOUND404Page does not exist or you don't have access
VALIDATION_ERROR400Invalid request body or parameters
PAGE_LIMIT_REACHED403Page count limit exceeded for your plan
FILE_SIZE_EXCEEDED413File exceeds the size limit for your plan

Retired endpoints return 410 Gone (see Upload a Page). Scoped keys that lack write access are rejected with 403 FORBIDDEN before any work is done.


Endpoints

Identity & Entitlements

GET /api/v1/me

Resolve the account, tier, usage, and per-tier entitlements for the current API key. Call this first: it tells you the username, tier, remaining quota, and which capabilities (visibilities, upload kinds, folders, fetch) your plan allows, so an agent can decide what it may do before attempting it. For a workspace-scoped key, the tier, usage, and entitlements reflect the workspace; username and email stay personal.

curl -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/me

Response 200:

{
  "data": {
    "username": "you",
    "email": "you@example.com",
    "tier": "pro",
    "pages_used": 8,
    "pages_limit": 1000,
    "storage_used": 5242880,
    "entitlements": {
      "maxFileSizeBytes": 104857600,
      "allowedVisibilities": ["private", "shared", "public"],
      "allowedKinds": ["document", "image", "video"],
      "maxVersionRetention": 25,
      "fetch": true,
      "folders": true
    },
    "storage": { "usedGb": 0.1, "capGb": 25, "addonGb": 0 }
  }
}

Error Cases:

  • 401 UNAUTHORIZED -- Missing or invalid API key

Upload a Page (streamed)

The legacy inline create endpoint has been retired and now returns 410 Gone. Upload via the streamed flow below, the CLI, or MCP.

Uploading is a streamed, three-step flow. The file bytes are PUT directly to object storage out-of-band, so there is no request-body size cap on the function.

Storing a large or opaque file you want handed back rather than rendered (a database dump, a build tarball, a .zip, or anything over 30 MB)? Use an archive instead. Archives (Pro and Team) stream in resumable parts up to 10 GB and are private download-only. See the Archives guide for the /api/archives/* lifecycle.

Supported file types: HTML (.html, .htm), MHTML web archives (.mhtml, .mht), Markdown (.md, .markdown), PDF (.pdf), JSON (.json), JSONL (.jsonl), source code and plain text (.js, .ts, .py, .css, .sql, .sh, .txt, and similar), Word documents (.docx), spreadsheets (.csv, .xlsx), and images (.png, .jpg, .jpeg, .webp, .gif, .avif, .bmp, .ico, .apng, .svg, .heic, .heif, .tif, .tiff). Documents (everything except images and video) upload on every tier; images and video require Pro.

Step 1 -- sign

POST /api/upload/sign

Reserve an object key, run the tier and storage gate, and mint a short-lived upload token.

Request Body (JSON):

FieldTypeRequiredDescription
filenamestringYesFilename including extension (e.g. report.pdf)
content_typestringYesMIME type (e.g. application/pdf)
size_bytesnumberYesExact body size in bytes -- must match the Content-Length of the PUT
workspace_idstringNoUpload to a specific workspace
curl -X POST \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"filename": "report.pdf", "content_type": "application/pdf", "size_bytes": 482190}' \
  https://sharedrop.cloud/api/upload/sign

The response includes an upload_url, a 5-minute upload_token, a finalize_url, and the object_key.

Step 2 -- PUT the bytes

Stream the file bytes directly to the upload_url returned by step 1, authenticating with the upload_token:

curl -X PUT "$UPLOAD_URL" \
  -H "Authorization: Bearer $UPLOAD_TOKEN" \
  -H "Content-Type: application/pdf" \
  -H "Content-Length: $(stat -f%z report.pdf)" \
  --data-binary @report.pdf

Step 3 -- finalize

POST /api/upload/finalize

Validate the token, sanitise the uploaded bytes, publish the page, and create the page row.

Request Body (JSON):

FieldTypeRequiredDescription
object_keystringYesThe object_key returned by step 1
upload_tokenstringYesThe upload_token returned by step 1
titlestringNoPage title. Defaults to the document title (HTML/PDF) or filename stem
visibilitystringNoprivate (default), shared, or public
modestringNoHTML only: static or interactive (default: your account's default upload mode, initially interactive; configure in Settings). Ignored for non-HTML kinds
workspace_idstringNoUpload to a specific workspace
page_idstringNoExisting page ID for re-upload (URL stays stable, version recorded)
slidesbooleanNoHTML only: publish as a slide deck (kind: slides) with fullscreen Present mode. Equivalent to a <meta name="sharedrop:kind" content="slides"> marker in the file. Ignored for non-HTML kinds, and ignored if sent to /sign instead of here
curl -X POST \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"object_key": "...", "upload_token": "...", "title": "Q4 Report", "visibility": "public"}' \
  https://sharedrop.cloud/api/upload/finalize

Response 201:

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "slug": "abc123",
    "title": "Q4 Report",
    "mode": "static",
    "file_size": 482190,
    "visibility": "public",
    "url": "/username/abc123",
    "full_url": "https://sharedrop.cloud/username/abc123",
    "created_at": "2026-04-07T00:00:00.000Z",
    "updated_at": "2026-04-07T00:00:00.000Z"
  }
}

Error Cases:

  • 400 VALIDATION_ERROR -- Invalid body, unsupported file type, or a size mismatch
  • 402 TIER_LIMIT -- Kind not allowed on your plan (e.g. an image on Free)
  • 403 PAGE_LIMIT_REACHED -- Page count limit exceeded
  • 413 FILE_SIZE_EXCEEDED -- File exceeds the size limit for your plan

List Pages

GET /api/v1/pages

List your pages with cursor-based pagination.

Query Parameters:

ParameterTypeDefaultDescription
limitnumber50Results per page (max 100)
cursorstring--Pagination cursor from previous response
workspace_idstring--Filter to a specific workspace
curl -H "Authorization: Bearer sd_..." \
  "https://sharedrop.cloud/api/v1/pages?limit=10"

Response 200:

{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "slug": "abc123",
      "title": "My Report",
      "mode": "static",
      "file_size": 2048,
      "visibility": "private",
      "url": "/username/abc123",
      "full_url": "https://sharedrop.cloud/username/abc123",
      "created_at": "2026-04-07T00:00:00.000Z",
      "updated_at": "2026-04-07T00:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "660e8400-e29b-41d4-a716-446655440001",
    "has_more": true
  }
}

To fetch the next page, pass cursor from pagination.next_cursor:

curl -H "Authorization: Bearer sd_..." \
  "https://sharedrop.cloud/api/v1/pages?limit=10&cursor=660e8400-e29b-41d4-a716-446655440001"

Get Page

GET /api/v1/pages/:id

Get metadata for a specific page.

curl -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000

Response 200:

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "slug": "abc123",
    "title": "My Report",
    "mode": "static",
    "file_size": 2048,
    "visibility": "private",
    "url": "/username/abc123",
    "full_url": "https://sharedrop.cloud/username/abc123",
    "created_at": "2026-04-07T00:00:00.000Z",
    "updated_at": "2026-04-07T00:00:00.000Z"
  }
}

Error Cases:

  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access

Fetch Page (raw content)

GET /api/v1/pages/:id/fetch

Mint a short-lived signed URL that returns a page's raw stored bytes -- for an agent pulling a page's content into its context. This is the agent-native counterpart to a human download: it returns the original file with its real content type, never a zip and never the sandboxed viewer wrapper. Available on every tier (advertised as entitlements.fetch in the GET /api/v1/me response).

The mint response does not contain the bytes -- it returns a fetch_url on the viewer origin that you then GET to stream the content. This mirrors the upload flow in reverse (mint → GET, like create_uploadPUT), so large pages never inflate the JSON response.

Access matches the rest of the API: the owner always may; a public page may be fetched by any authenticated caller; a shared/private page requires an active access grant matching one of your verified emails. Every denial returns 404.

# 1. Mint the fetch URL
curl -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000/fetch

Response 200:

{
  "data": {
    "fetch_url": "https://view.sharedrop.cloud/api/fetch/550e8400-e29b-41d4-a716-446655440000?token=...",
    "expires_at": "2026-04-07T00:05:00.000Z",
    "content_type": "text/html; charset=utf-8",
    "mode": "static",
    "size": 2048
  }
}
# 2. Pull the raw bytes -- no auth header, the URL token is the credential
curl "https://view.sharedrop.cloud/api/fetch/550e8400-...?token=..." -o page.html

The pull endpoint streams the bytes with Content-Disposition: attachment, X-Content-Type-Options: nosniff, and Content-Security-Policy: sandbox so the content can never execute or make network requests. The token is single-purpose and expires after ~5 minutes; an invalid, expired, or wrong-page token returns 404.

Error Cases:

  • 401 UNAUTHORIZED -- Missing or invalid API key (mint endpoint)
  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access; or an invalid/expired fetch token (pull endpoint)

Download Page (zip)

GET /api/v1/pages/:id/download

Stream a page's complete artefact as a zip -- the root document plus every asset (images, CSS, JS) beneath it, with their original relative paths preserved. This is the human-facing counterpart to fetch: download returns the whole bundle, fetch returns just the raw root document. The response body is the zip bytes (Content-Type: application/zip), not a JSON envelope.

Access matches the rest of the API: you can download a page you own, or one shared to you with zip download enabled (the sharer ticks "allow zip download" on your access grant, matched against your account's primary verified email). Every denial -- unauthenticated, no page, not authorised, or an empty page -- returns 404 PAGE_NOT_FOUND, so existence is never leaked.

curl -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000/download \
  -o report.zip

Error Cases:

  • 401 UNAUTHORIZED -- Missing or invalid API key
  • 404 PAGE_NOT_FOUND -- Page does not exist, you can't download it, or it has no downloadable content

Update Page

PATCH /api/v1/pages/:id

Update a page's title or visibility.

Request Body (JSON):

FieldTypeDescription
titlestringNew page title
visibilitystringpublic, private, or shared

Both fields are optional. Include only the fields you want to change.

curl -X PATCH \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Title", "visibility": "public"}' \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000

Response 200:

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "slug": "abc123",
    "title": "Updated Title",
    "mode": "static",
    "file_size": 2048,
    "visibility": "public",
    "url": "/username/abc123",
    "full_url": "https://sharedrop.cloud/username/abc123",
    "created_at": "2026-04-07T00:00:00.000Z",
    "updated_at": "2026-04-07T00:00:00.000Z"
  }
}

Error Cases:

  • 400 VALIDATION_ERROR -- Invalid JSON body or field values
  • 403 VALIDATION_ERROR -- Visibility not available on your plan (e.g., private on Free tier)
  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access

Delete Page

DELETE /api/v1/pages/:id

Permanently delete a page and its stored HTML. This also deletes all versions, comments, shares, and access grants.

curl -X DELETE \
  -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000

Response 200:

{
  "data": {
    "success": true
  }
}

Error Cases:

  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access

Share Page

POST /api/v1/pages/:id/share

Share a page with someone by email address. Creates an access grant that allows the recipient to view the page.

Request Body (JSON):

FieldTypeRequiredDescription
emailstringYesEmail address of the person to share with
curl -X POST \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"email": "colleague@example.com"}' \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000/share

Response 201:

{
  "data": {
    "id": "grant-uuid",
    "email": "colleague@example.com",
    "status": "pending",
    "created_at": "2026-04-07T00:00:00.000Z"
  }
}

The grant starts as pending. When the recipient signs in to sharedrop with a matching email, the grant automatically activates and they can view the page.

Notifications (best-effort): A successful share always creates the access grant. On top of that, sharedrop makes two best-effort attempts that are not part of the synchronous response and are not guaranteed to be delivered:

  • It fires an access.granted webhook to the page owner's configured webhook and Telegram destinations (see the Webhooks guide).
  • It sends a gated invitation email to the recipient with a one-click unsubscribe link. This email is gated by the owner's notification preference, the recipient's suppression status (a prior unsubscribe, bounce, or complaint), a 24-hour per-recipient cooldown, and a daily cap, so it may not be sent for a given share.

Error Cases:

  • 400 VALIDATION_ERROR -- Invalid email address
  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access

List Shares

GET /api/v1/pages/:id/shares

List the active access grants for a page (revoked grants are excluded). Owner-only.

curl -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000/shares

Response 200:

{
  "data": [
    {
      "id": "grant-uuid",
      "email": "colleague@example.com",
      "status": "active",
      "created_at": "2026-04-07T00:00:00.000Z",
      "updated_at": "2026-04-07T00:00:00.000Z"
    }
  ]
}

Error Cases:

  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access

Reservations

Reserve a placeholder /{username}/{slug} address before any content exists, hand out the final URL up front, and let the first upload claim it into a live page. See the Reserved addresses guide for the end-to-end walkthrough and the placeholder/expiry behavior.

Reads (GET) require a pages:read scope; mutations (POST, PATCH, DELETE) require pages:write. Every reservation carries a lifecycle status:

StatusMeaning
reservedWaiting for its first upload
claimedAlready claimed into a live page (claimed_page_id is set)
revokedManually revoked; the claim token is dead
expiredPassed its expires_at unclaimed; the slug is freed and the claim token is dead

Revoked and expired rows stay in the list for history.

Create a Reservation

POST /api/v1/reservations

Reserve an address and mint a one-time claim credential. Returns 201 with the serialized reservation (including the final url) plus a sibling claim_token (an sdr_ credential). The token is shown here once, never appears in any later response, and is never logged: store it now and hand it to the agent over a secure channel.

Request Body (JSON): every field is optional.

FieldTypeDescription
titlestringPage title shown once the address is claimed (default Untitled)
descriptionstringOptional description for your own reference
intended_agent_namestringName of the agent this address is held for (shown in the dashboard)
visibilitystringprivate (default), shared, or public. The claimed page inherits it
modestringHTML render mode the claimed page takes: static or interactive
watermark_enabledbooleanWhether the claimed page is watermarked (default false)
folder_idstringPro folder to file the claimed page into
expires_atstringISO 8601 datetime after which an unclaimed reservation expires. Omit to keep it until claimed or revoked
curl -X POST \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"title": "Q3 Metrics", "intended_agent_name": "reporting-bot", "visibility": "public"}' \
  https://sharedrop.cloud/api/v1/reservations

Response 201:

{
  "data": {
    "reservation": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "slug": "abc123",
      "title": "Q3 Metrics",
      "description": null,
      "intended_agent_name": "reporting-bot",
      "visibility": "public",
      "mode": "static",
      "watermark_enabled": false,
      "status": "reserved",
      "claimed_page_id": null,
      "url": "/username/abc123",
      "full_url": "https://sharedrop.cloud/username/abc123",
      "expires_at": null,
      "created_at": "2026-07-19T00:00:00.000Z",
      "updated_at": "2026-07-19T00:00:00.000Z"
    },
    "claim_token": "sdr_example_shown_once_store_now"
  }
}

The agent claims the address by streaming its first upload with this reservation, either by passing reservation_id to the MCP create_upload tool or the CLI --to flag, or by presenting the sdr_ token as the Bearer credential to the sign/finalize endpoints. The finished upload becomes a new page at the reserved URL, inheriting the visibility, mode, and folder chosen here.

Error Cases:

  • 400 VALIDATION_ERROR -- Invalid body or an out-of-range field
  • 402 TIER_LIMIT -- Over your plan's active-reservation cap, or a visibility your plan does not allow. Free allows 2 active reservations, Pro allows 25, Team is unlimited. The 402 carries a billing envelope with an upgrade URL

List Reservations

GET /api/v1/reservations

List your reservations with cursor-based pagination. Includes reserved, claimed, revoked, and expired rows.

Query Parameters:

ParameterTypeDefaultDescription
limitnumber50Results per page (max 100)
cursorstring--Pagination cursor from the previous response
curl -H "Authorization: Bearer sd_..." \
  "https://sharedrop.cloud/api/v1/reservations?limit=20"

The response is the standard paginated envelope: data is an array of serialized reservations (the same shape as create, without the claim_token), and pagination carries next_cursor and has_more.

Get a Reservation

GET /api/v1/reservations/:id

Fetch a single reservation's metadata. A missing or non-owned id returns 404 (existence is never leaked).

curl -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/reservations/550e8400-e29b-41d4-a716-446655440000

Error Cases:

  • 404 NOT_FOUND -- Reservation does not exist or you don't have access

Update a Reservation

PATCH /api/v1/reservations/:id

Update reservation metadata. The slug (the address itself) is immutable and can never be changed: that stability is the whole point of a reservation. Only title, description, intended_agent_name, visibility, mode, and watermark_enabled can be set. Include only the fields you want to change.

curl -X PATCH \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"title": "Q3 Metrics (final)", "visibility": "shared"}' \
  https://sharedrop.cloud/api/v1/reservations/550e8400-e29b-41d4-a716-446655440000

Error Cases:

  • 400 VALIDATION_ERROR -- Invalid JSON body or field values
  • 402 TIER_LIMIT -- A visibility your plan does not allow (carries a billing envelope)
  • 404 NOT_FOUND -- Reservation does not exist or you don't have access

Revoke a Reservation

POST /api/v1/reservations/:id/revoke

Revoke a reservation. The row is kept (status flips to revoked) and the claim token is permanently invalidated in the same operation, so the sdr_ credential dies the instant this returns 200. Use revoke as the manual equivalent of expiry.

curl -X POST \
  -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/reservations/550e8400-e29b-41d4-a716-446655440000/revoke

Returns 200 with the serialized reservation (now revoked).

Error Cases:

  • 404 NOT_FOUND -- Reservation does not exist, is already terminal, or you don't have access

Delete a Reservation

DELETE /api/v1/reservations/:id

Remove a reservation row entirely. Unlike revoke, this deletes the record rather than keeping it for history.

curl -X DELETE \
  -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/reservations/550e8400-e29b-41d4-a716-446655440000

Returns 200 with { "data": { "success": true } }.

Error Cases:

  • 404 NOT_FOUND -- Reservation does not exist or you don't have access

Revoke Share

DELETE /api/v1/pages/:id/shares/:grantId

Revoke an access grant by its id. Idempotent: revoking an already-revoked or unknown grant still returns success.

curl -X DELETE \
  -H "Authorization: Bearer sd_..." \
  https://sharedrop.cloud/api/v1/pages/550e8400-e29b-41d4-a716-446655440000/shares/grant-uuid

Response 200:

{
  "data": {
    "success": true
  }
}

Error Cases:

  • 404 PAGE_NOT_FOUND -- Page does not exist or you don't have access