Skip to content
Slingdrop

Reference

SDK reference

Every method of the @slingdrop/sdk TypeScript SDK.

Install#

terminal
npm i @slingdrop/sdk

ESM only, with TypeScript types. Runs in Node 20 or later and modern browsers (it uses fetch, Blob and Web Crypto). Keep API keys on the server; in a browser, use it without a key or with signed upload URLs.

The client#

client.ts
import { Slingdrop } from "@slingdrop/sdk";

const sd = new Slingdrop({
  apiKey: process.env.SLINGDROP_API_KEY, // sd_…
  // baseUrl: "https://slingdrop.my",    // default; requests go to <baseUrl>/api/v1
  // fetch: myFetch,                     // optional custom fetch (tests, proxies)
});

Without an API key only drops.create works: it sends an anonymous drop (public or passcode, up to 24 hours), like the website does for signed-out visitors. sd.anonymous tells you which mode you're in.

MethodCallsReturns
me()GET /me{ id, email, name, plan, limits }
request<T>(path, init?)Any /v1 pathThe parsed JSON. Use it for endpoints without a helper.

drops#

MethodCallsReturns
drops.create(files, options?)POST /drops + uploads{ drop, shareUrl, ownerKey }
drops.addVersion(id, files, note?)POST /drops/{id}/versions + uploads{ id, number }
drops.list(status?)GET /drops?status= ("active" or "all")Drop[] (with fileCount)
drops.get(id)GET /drops/{id}Drop with files and versions
drops.update(id, patch)PATCH /drops/{id}The updated drop
drops.revoke(id)POST /drops/{id}/revoke{ ok }
drops.delete(id, { forever? })DELETE /drops/{id}{ ok, trashed }
drops.analytics(id, days?)GET /drops/{id}/analytics (default 30 days)The report
drops.comments(id)GET /drops/{id}/commentsComments with anchors

drops.create#

files is a list of { name, data: Blob, type? }. A / in name keeps folder structure. In Node, await fs.openAsBlob(path) reads big files lazily. Uploads use multipart with 4 parts in flight for files over 10 MB, and retry with backoff.

OptionTypeNotes
titlestring
purpose"share" | "feedback"Default "share" (the REST API's default is "feedback").
expiry"1h" … "90d" | "forever" | "burn"Default forever for share drops, 7d for feedback, 24h without a key.
passcodestringMakes it a passcode drop.
recipientsstring[]Emails; makes it private (viewer role).
vaultbooleanEnd-to-end encrypts in this process. The key is only in the returned shareUrl (after #k=).
allowDownloadbooleanDefault true.
allowCommentbooleanDefaults to on for feedback drops.
allowEditboolean
requireApprovalboolean
folderIdstringOne of folders.list().
source"api" | "cli" | "mcp"Default api.
organizationIdstringSend as a team.
onProgress(loaded, total) => voidUpload progress in bytes.
signalAbortSignalCancel the upload.
vault.ts
const { shareUrl } = await sd.drops.create([{ name: "contract.pdf", data }], { vault: true, expiry: "7d" });
// shareUrl = https://slingdrop.my/d/…#k=…   ← only you have this key. Slingdrop can't read the file.

folders#

MethodCallsReturns
folders.list()GET /foldersFolder[]: id, name, color, sortOrder, dropCount, createdAt
folders.create(name, color?)POST /foldersFolder

File a drop with drops.update(id, { folderId }) (null unfiles it). Rename or delete folders with sd.request; see Folders.

reviews#

MethodCallsReturns
reviews.start(dropId, opts?)POST /drops/{id}/review. { agentName?, question?, client?, createdVia? }ReviewState
reviews.feedback(dropId, { since?, wait? })GET /drops/{id}/feedback. wait up to 60 seconds.ReviewState
reviews.round(dropId, { note?, resolves? })POST /drops/{id}/rounds, after addVersionReviewState
reviews.reply(dropId, commentId, text)POST …/comments/{commentId}/replies{ id }
reviews.resolve(dropId, commentId, resolved?)POST …/comments/{commentId}/resolve{ ok }
reviews.approvals(dropId)GET /drops/{id}/approvalsApprovals

The loop, the ReviewState shape and a full example are in Reviews & approvals.

hosting#

MethodCallsReturns
hosting.spaces()GET /hosting/spacesHostingSpace[]
hosting.createSpace(slug?)POST /hosting/spacesHostingSpace (your default space when no slug)
hosting.deleteSpace(space)DELETE /hosting/spaces/{space}{ ok }
hosting.usage()GET /hosting/usageStorage, bandwidth, resizes this month
hosting.put(space, path, data, opts?)Upload (parts over 95 MB). { contentType?, immutable?, template?, onProgress? }HostedObject with url
hosting.list(space, { prefix?, after?, limit? })GET /hosting/{space}/objects{ objects, next }
hosting.delete(space, path)DELETE /hosting/{space}/objects/{path}{ ok }
hosting.createUploadUrl(space, opts?)POST /hosting/{space}/upload-urls. { path?, prefix?, filename?, contentType?, maxBytes?, expiresIn? }UploadUrl
hosting.signUrl(space, path, { expiresIn? })POST /hosting/{space}/sign (60 s to 7 days, default 1 hour){ url, signed, expiresAt, template }
hosting.access(space)GET /hosting/{space}/access{ defaultTemplateId, rules }
hosting.setAccess(space, access)PUT /hosting/{space}/accessSpaceAccess
hosting.setFileAccess(space, path, template)PATCH /hosting/{space}/objects/{path}; null clears itThe file's effective access

hosting.templates#

MethodCallsReturns
templates.list()GET /hosting/templatesAccessTemplate[]
templates.create(input)POST /hosting/templatesAccessTemplate plus secret (signed and webhook templates, shown once)
templates.update(id, patch)PATCH /hosting/templates/{id}AccessTemplate
templates.delete(id)DELETE /hosting/templates/{id}{ ok }
templates.test(id, { token, path, jwt? })POST /hosting/templates/{id}/testWhether the CDN would allow that token for that path, and why
templates.rotateSecret(id)POST /hosting/templates/{id}/rotateThe new secret
templates.setDefault(id | null)PUT /hosting/default-template{ ok } (null = Public)

Guides: File hosting and Access rules.

requests#

MethodCallsReturns
requests.list()GET /requestsFile requests
requests.create(input)POST /requests. { title, instructions?, items?, deadlineAt?, visibility?, passcode?, allowedEmails? }{ id, url }

webhooks#

MethodCallsReturns
webhooks.list()GET /webhooks{ data, events }
webhooks.create(url, events)POST /webhooks{ id, secret } (secret shown once)
webhooks.delete(id)DELETE /webhooks/{id}{ ok }
webhooks.deliveries(id)GET /webhooks/{id}/deliveriesRecent attempts

Standalone helpers#

verifyWebhook(secret, signature, body, toleranceSec?)#

Checks a Slingdrop-Signature header against the raw body, in constant time. Rejects deliveries more than toleranceSec (default 300) seconds old. Returns a boolean.

webhook.ts
import { verifyWebhook } from "@slingdrop/sdk";

const body = await req.text(); // the raw body, before JSON.parse
const ok = await verifyWebhook(process.env.SLINGDROP_WEBHOOK_SECRET!, req.headers.get("slingdrop-signature"), body);

signHostedUrl(url, secret, { expiresIn? })#

Signs a hosted file URL locally with a signed-links template's secret, no API call. expiresIn is in seconds (default 3600).

sign.ts
import { signHostedUrl } from "@slingdrop/sdk";

const url = await signHostedUrl("https://cdn.slingdropcontent.my/acme/private/report.pdf", process.env.SLINGDROP_SIGNING_SECRET!, { expiresIn: 600 });
// → …/acme/private/report.pdf?exp=1767225600&sig=…

Errors#

Failed calls throw SlingdropError, with the server's message, the HTTP status and a code such as rate_limited or plan_required.

errors.ts
import { SlingdropError } from "@slingdrop/sdk";

try {
  await sd.drops.create(files, { expiry: "forever" });
} catch (err) {
  if (err instanceof SlingdropError && err.code === "storage_full") {
    // tell the user to free up space
  } else throw err;
}

Exported types#

SlingFile, CreateOptions, Drop, Folder, ReviewState, ReviewComment, HostedObject, HostingSpace, HostingUsage, UploadUrl, SignedUrl, SpaceAccess, AccessMode, AccessTemplate, AccessTemplateInput, JwtConfig, JwtProvider, JwtRule and TemplateTestResult.

Prefer another language? Generate a client from the OpenAPI spec, or try requests in the API reference.