Sharing & feedback
Drops
Create and update drops, upload files, publish versions, and control expiry, visibility, purpose and folders.
A drop is a link to one or more files. It has a current version, an optional history of earlier versions, an access setting (public, passcode or named people), an expiry, and a purpose that decides whether people are asked for feedback.
Endpoints#
/dropsCreate a drop and get upload targets/dropsList your drops/drops/{id}A drop with its files and versions/drops/{id}Change access, purpose, expiry or folder/drops/{id}/versionsPublish a new version behind the same link/drops/{id}/revokeTurn off the link/drops/{id}Move to Trash, or delete with ?forever=1/drops/{id}/analyticsViews, viewers, dwell time and devices/drops/{id}/commentsEvery comment, with where it's pinnedPaths are relative to https://api.slingdrop.my/v1. Try them in the API reference.
Create a drop#
Creating a drop is two steps: describe the files and get back the link plus one upload target per file, then send each file's bytes. The link exists immediately; viewers see the drop once every file is in. The SDK and CLI do both steps for you.
curl https://api.slingdrop.my/v1/drops \
-H "Authorization: Bearer $SLINGDROP_API_KEY" \
-H "content-type: application/json" \
-d '{
"title": "Launch assets",
"purpose": "share",
"visibility": "private",
"recipients": [{ "email": "ana@studio.com", "role": "commenter" }],
"expiry": "30d",
"files": [
{ "name": "logo.svg", "size": 18211, "type": "image/svg+xml", "relativePath": "brand/logo.svg" },
{ "name": "hero.mp4", "size": 48211990, "type": "video/mp4" }
]
}'import { openAsBlob } from "node:fs";
import { Slingdrop } from "@slingdrop/sdk";
const sd = new Slingdrop({ apiKey: process.env.SLINGDROP_API_KEY });
const { drop, shareUrl } = await sd.drops.create(
[
{ name: "brand/logo.svg", data: await openAsBlob("brand/logo.svg") }, // a "/" keeps the folder
{ name: "hero.mp4", data: await openAsBlob("hero.mp4") },
],
{
title: "Launch assets",
recipients: ["ana@studio.com"], // makes it private
expiry: "30d",
onProgress: (loaded, total) => console.log(Math.round((loaded / total) * 100) + "%"),
},
);slingdrop ./brand hero.mp4 --title "Launch assets" \
--private ana@studio.com --expires 30dRequest body#
filesarrayrequired- 1–2,000 entries of
{ name, size, type?, relativePath? }.sizeis in bytes and must match what you upload.relativePathkeeps folder structure. titlestring- Shown on the drop page. Defaults to the first file name (“logo + 1 more”).
purpose"share" | "feedback"- Default
"feedback"on the REST API (the SDK and CLI default to"share"). See Purpose. visibility"public" | "passcode" | "private"- Default
"public". See Who can open it. passcodestring- 4–12 characters. Required when visibility is passcode.
recipientsarray{ email, role }with roleviewer,commenteroreditor. Required (at least one) when visibility is private. They're emailed once the files are in.expirystring1h,24h,7d,30d,90d,foreverorburn. See Expiry.folderIdstring | null- File it in one of your folders. Left out, your filing rules may file it.
source"cli" | "mcp"- Which client made it. See Source.
notestring- A message shown with the first version (up to 2,000 characters).
allowDownloadboolean- Default
true.falsemakes it view-only. allowCommentboolean- Defaults to on for feedback drops and off for share drops.
requireApprovalboolean- Ask recipients to approve. Default
false. See Reviews. allowEditboolean- Let editors change docs and notes. Default
false. notifyOnViewboolean- Email you when it's opened. Default
true. watermark"off" | "overlay" | "burned"- Viewer watermark.
burnedneeds Pro or Team. encryption"none" | "vault"- Vault drops are encrypted on your machine; use the SDK's
vault: trueor the CLI's--vaultrather than doing it by hand. organizationIdstring- Send as a team you belong to. Team policies (allowed visibility, expiry, domains) apply.
replyTostring- Link id of a drop this one answers. Its owner is notified.
boostboolean- Spend a Boost bought in the app: one drop up to 10 GB, live for 14 days.
Response#
{
"drop": { "id": "k7Qm2xPa", "linkId": "k7Qm2xPa", "url": "https://slingdrop.my/d/k7Qm2xPa", "expiresAt": "2026-11-20T09:12:00.000Z", "folderId": null },
"ownerKey": "…",
"files": [
{ "id": "F1", "mode": "single", "partSize": 8388608, "token": "T1" },
{ "id": "F2", "mode": "multipart", "partSize": 8388608, "token": "T2" }
]
}files is in the same order as your request. folderId tells you if a filing rule filed it.
Uploads#
Each upload target carries a token, valid for seven days. Send it as x-upload-token. Uploads go to https://slingdrop.my/api/uploads/{fileId} and don't need your API key.
- single (10 MB or less):
PUT /api/uploads/{fileId}with the raw bytes. TheContent-Lengthmust equal the declared size, or you get400 size_mismatch. - multipart (over 10 MB):
PUT /api/uploads/{fileId}/parts/{n}for eachpartSizeslice, numbered from 1, thenPOST /api/uploads/{fileId}/completewith the parts. Parts can go up in parallel.DELETE /api/uploads/{fileId}abandons the upload.
# A file over 10 MB comes back as { "id": "F2", "mode": "multipart", "partSize": 8388608, "token": "T2" }.
# Upload each 8 MB slice (the last may be smaller), numbered from 1:
curl -X PUT https://slingdrop.my/api/uploads/F2/parts/1 -H "x-upload-token: T2" --data-binary @part1
# → { "partNumber": 1, "etag": "…" }
curl -X PUT https://slingdrop.my/api/uploads/F2/parts/2 -H "x-upload-token: T2" --data-binary @part2
# …
# Then finish with every part's etag:
curl -X POST https://slingdrop.my/api/uploads/F2/complete \
-H "x-upload-token: T2" -H "content-type: application/json" \
-d '{ "parts": [{ "partNumber": 1, "etag": "…" }, { "partNumber": 2, "etag": "…" }] }'Repeating a finished upload is harmless: it answers { "ok": true }. When the last file of a drop is in, the drop becomes active, private recipients are emailed and a drop.ready webhook fires.
Purpose: share or feedback#
| share | feedback | |
|---|---|---|
| For | Sending and keeping files | Asking for comments and approval |
| Comments | Off unless allowComment is true | On unless allowComment is false |
| Default expiry (REST, signed in) | Kept until you delete it | 24 hours |
| Default expiry (SDK / CLI, signed in) | Kept until you delete it | 7 days |
Switch later with PATCH { "purpose": "share" }, which also turns comments and approval off unless the same update sets them. Switching to feedback turns comments on.
Who can open it#
public: anyone with the link.passcode: anyone with the link and the passcode (4–12 characters).private: only the listedrecipients, who sign in with that email. Free allows 5 recipients per drop; Pro and Team are effectively unlimited.
Vault drops (encryption: "vault") are end-to-end encrypted: the key lives only in the #k= part of the share URL the SDK returns, so Slingdrop can't read them, preview them or search them, and can't email the link for you.
Expiry#
| Value | Meaning |
|---|---|
1h | One hour from now (also: 24h, 7d, 30d, 90d). |
forever | Kept until you delete it, within your storage. Signed-in plans only. |
burn | Burn after reading: the first person to open it gets 30 minutes, then the link is gone. Create only. |
Without an account, drops last at most 24 hours. PATCH with a new expiry resets the clock from now. An expired drop stops opening; drops you delete go to Trash and can be restored in the app for 30 days.
Update a drop#
curl -X PATCH https://api.slingdrop.my/v1/drops/$DROP_ID \
-H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
-d '{ "visibility": "passcode", "passcode": "tiger42", "expiry": "7d", "allowDownload": false }'await sd.drops.update(dropId, { visibility: "passcode", passcode: "tiger42", expiry: "7d", allowDownload: false });You can change title, purpose, visibility, passcode, recipients (replaces the list; new people are emailed), allowDownload, allowComment, allowEdit, requireApproval, notifyOnView, watermark, expiry (not burn), pinnedToProfile and folderId. Access changes are written to the audit log. The response is the updated drop in data.
Versions#
A new version replaces the files behind the same link. Open comments move to the file with the same path in the new version, people who have the drop are told, and a version.created webhook fires.
curl https://api.slingdrop.my/v1/drops/$DROP_ID/versions \
-H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
-d '{ "note": "Tighter kerning on the logo", "files": [{ "name": "logo.svg", "size": 18004, "type": "image/svg+xml" }] }'
# → { "version": { "id": "…", "number": 2 }, "files": [{ "id": "F3", "mode": "single", "token": "T3", … }] }
# Upload the bytes exactly as for a new drop. Version 2 goes live when its last file is in.const version = await sd.drops.addVersion(dropId, [{ name: "logo.svg", data: await openAsBlob("logo.svg") }], "Tighter kerning on the logo");
console.log(version.number); // 2Free keeps the last 3 versions (older ones are deleted when a new one goes live); Pro and Team keep them all. GET /drops/{id} lists them. The drop must be active, or you get 409 inactive.
Folders#
Pass folderId when creating or updating a drop to file it; null on update unfiles it. List one folder with GET /drops?folderId={id}, or unfiled drops with ?folderId=none. When you create a drop without a folder, your filing rules run. Only the sender can file a drop.
Source#
Every drop records where it came from: web, mobile, email, request, api, cli or mcp. Over the API you may only claim cli or mcp; anything else is recorded as api. The library can filter by source, and filing rules can match on it (for example “everything from my agents goes in AI work”). It's returned as source on GET /drops/{id}.
List and read#
GET /drops returns up to 200 of your most recent live drops in data; add ?status=all to include expired, revoked and trashed ones. GET /drops/{id} adds files and versions.
const drops = await sd.drops.list(); // or sd.drops.list("all")
const drop = await sd.drops.get(drops[0]!.id); // with files and versions
const report = await sd.drops.analytics(drop.id, 7);
const comments = await sd.drops.comments(drop.id);Analytics take ?days= (1–365, default 30). Free gets the basic report; Pro and Team get the full one with per-viewer and per-page detail.
Revoke or delete#
# Turn the link off (the drop stays in your library)
curl -X POST https://api.slingdrop.my/v1/drops/$DROP_ID/revoke -H "Authorization: Bearer $SLINGDROP_API_KEY"
# Move to Trash (restorable for 30 days) or delete right away
curl -X DELETE "https://api.slingdrop.my/v1/drops/$DROP_ID" -H "Authorization: Bearer $SLINGDROP_API_KEY"
curl -X DELETE "https://api.slingdrop.my/v1/drops/$DROP_ID?forever=1" -H "Authorization: Bearer $SLINGDROP_API_KEY"await sd.drops.revoke(dropId);
await sd.drops.delete(dropId); // → { ok: true, trashed: true }
await sd.drops.delete(dropId, { forever: true }); // → { ok: true, trashed: false }slingdrop revoke <id>
slingdrop rm <id> # Trash
slingdrop rm <id> --forever # delete nowFile requests#
The other direction: a link people use to upload files to you. Each upload becomes a drop in your library (source request).
curl https://api.slingdrop.my/v1/requests \
-H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
-d '{ "title": "Brand assets", "instructions": "Logo in SVG please", "deadlineAt": "2026-11-01T17:00:00Z",
"items": [{ "label": "Logo", "required": true, "acceptedTypes": [".svg"] }] }'
# → { "id": "…", "url": "https://slingdrop.my/r/…" }Fields: title (required), instructions, visibility and passcode, allowedEmails, maxSizeBytes, deadlineAt, remindersEnabled, items (a checklist, Pro and Team) and organizationId. publicNameMode (full, first, custom or hidden) with publicName for a custom name sets how you appear to uploaders on this request; leave it out to use your account setting. Your email is never shown. PATCH /requests/{id} closes or reopens one, POST /requests/{id}/nudge reminds invited people who haven't uploaded (at most every 6 hours), and DELETE removes it while keeping uploaded drops. Free allows one open request at a time.
Common errors#
| Status | code | When |
|---|---|---|
| 400 | invalid | A field failed validation; the message names it. |
| 401 | login_required | A feature that needs an account, e.g. forever without a signed-in owner. |
| 403 | plan_required | Over a plan limit: expiry, recipients, burned-in watermark. |
| 404 | not_found | Not your drop, or no such drop. |
| 413 | too_large | The drop is bigger than your plan allows per drop. |
| 413 | storage_full | Your storage is full. |
| 429 | daily_limit | You've hit your plan's drops per 24 hours. |
Errors always carry error (a sentence you can show) and code. The SDK throws them as SlingdropError with status and code.