Skip to content
Slingdrop

Sharing & feedback

Reviews & approvals

Ask for feedback, read comments and approvals, and wait for a reviewer from code.

A review is a drop that asks for feedback: reviewers open the link in any browser, pin comments on the exact spot (a pixel, a PDF page, a timestamp, a line of code) and approve it or request changes. From code you start the review, wait for that feedback, publish the next version behind the same link and repeat until it is approved. This is the same loop the MCP tools run for AI agents.

Feedback drops#

Every drop has a purpose. A "share" drop just delivers files. A "feedback" drop turns comments on by default. Approval is a separate switch, requireApproval, which adds the Approve and Request changes buttons for reviewers.

purpose"share" | "feedback"
REST default is "feedback"; the SDK and CLI default to "share". With "feedback", allowComment defaults to true.
allowCommentboolean
Override the purpose's default for comments.
requireApprovalboolean
Defaults to false. Reviewers can only record a decision when this is on. Starting a review (below) turns it on for you.

You can change all three later with PATCH /drops/{id}. Switching a drop to "share" turns comments and approval off unless the same update sets them. See Drops for every other setting.

Who can approve

People you shared the drop with record decisions. You can't approve your own drop, with one exception: a drop you started a review on with POST /drops/{id}/review, where you are the person reviewing what your agent or automation made.

The review loop#

  1. Create a feedback drop and upload the files

    POST /drops with "purpose": "feedback", then upload each file to its target (the SDK does both in drops.create).

  2. Start the review

    POST /drops/{id}/review labels the drop with your agent's name and question, turns on comments and approval, records round 1 and notifies you.

  3. Wait for feedback

    GET /drops/{id}/feedback?since=<cursor>&wait=60 returns as soon as someone comments or decides, or after the wait.

  4. Send the next version

    POST /drops/{id}/versions plus uploads, then POST /drops/{id}/rounds with the comment ids the version addresses. Back to step 3 until the status is approved.

Start a review#

POST/drops/{id}/reviewTurn an uploaded drop into a review
agentNamestring, 1–60
Shown to reviewers as the author. Default "Your AI agent".
questionstring, up to 2,000
What you want the reviewer to look at or decide, shown at the top of the review.
clientstring, up to 120
Free-form name of the tool making the request, e.g. "release-bot/2.1".
createdVia"api" | "mcp"
Default "api".

It answers 201 with the review state. Calling it again on the same drop updates the name and question.

curl
# 1. Create a feedback drop (then upload the file to the returned target, see Drops)
curl https://api.slingdrop.my/v1/drops \
  -H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
  -d '{"title":"Homepage hero","purpose":"feedback","expiry":"30d","files":[{"name":"hero.png","size":482113,"type":"image/png"}]}'

curl -X PUT https://slingdrop.my/api/uploads/$FILE_ID \
  -H "x-upload-token: $UPLOAD_TOKEN" --data-binary @hero.png

# 2. Start the review
curl https://api.slingdrop.my/v1/drops/$DROP_ID/review \
  -H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
  -d '{"agentName":"Release bot","question":"Is the headline readable on mobile?"}'

Read and wait for feedback#

GET/drops/{id}/feedbackStatus, latest decision and comments
sinceISO date-time (query)
The cursor from your last response. Returns only new feedback, plus every comment that is still open, so you always see the full to-do list.
waitseconds, 0–60 (query)
Hold the request open until a person comments or decides after since, or until the time is up. Values above 60 are treated as 60.

Waits return early as soon as something arrives, and the response has newFeedback: true when it did. If the wait ended with nothing new, just call again with the same cursor. Your own replies don't count as new feedback. While you wait, reviewers see that your agent is listening.

curl
curl "https://api.slingdrop.my/v1/drops/$DROP_ID/feedback?since=$CURSOR&wait=60" \
  -H "Authorization: Bearer $SLINGDROP_API_KEY"

The review state#

POST /review, GET /feedback and POST /rounds all return the same object under data:

200 OK
{
  "data": {
    "dropId": "k3f9xq",
    "url": "https://slingdrop.my/d/k3f9xq",
    "title": "Homepage hero",
    "agent": { "name": "Release bot", "question": "Is the headline readable on mobile?" },
    "status": "changes_requested",
    "currentVersion": { "id": "v_01", "number": 1 },
    "decision": { "decision": "changes_requested", "note": "Almost there", "by": "Ana", "version": 1, "at": "2026-10-21T09:14:03.000Z" },
    "openComments": 1,
    "comments": [
      {
        "id": "c_8k2",
        "file": "hero.png",
        "version": 1,
        "author": "Ana",
        "fromAgent": false,
        "text": "Make the headline bigger",
        "where": "pinned 30% from the left and 12% from the top",
        "anchor": { "type": "image_pin", "x": 0.3, "y": 0.12 },
        "resolved": false,
        "createdAt": "2026-10-21T09:13:40.000Z",
        "replies": []
      }
    ],
    "rounds": [{ "number": 1, "note": "Is the headline readable on mobile?", "resolved": [], "at": "2026-10-21T09:02:11.000Z" }],
    "cursor": "2026-10-21T09:14:03.000Z",
    "newFeedback": true
  }
}
FieldMeaning
statusThe decision on the current version: waiting_for_review, changes_requested or approved. A new version starts as waiting_for_review again.
decisionThe latest decision on the current version, or null.
openCommentsUnresolved comments from people (not your own replies).
comments[].whereThe comment's location in words, e.g. at 1:42, page 3, 40% from the left…, lines 12–18 or the whole file. The raw position is in anchor.
comments[].repliesReplies in the thread. A comment carried onto newer versions appears once, with all its replies.
cursorPass as since next time.
newFeedbackOnly on calls with wait: whether something new arrived.

Send a new version#

POST/drops/{id}/versionsUpload a new version behind the same link
POST/drops/{id}/roundsRecord the round and resolve what it addressed

POST /versions takes { note?, files } (the same file list as creating a drop) and returns { version: { id, number }, files } with one upload target per file. The version goes live when its last file is uploaded; open comments are carried over to it. Then record the round:

notestring, up to 4,000
What changed, shown to the reviewer.
resolvesstring[], up to 200
Comment ids this version addresses. Each is resolved and gets a reply from your agent: "Addressed in version 2."

The reviewer is notified again. POST /rounds answers 409 with not_ready if the new version hasn't finished uploading (retry after a second), and 409 conflict if that version was already recorded or the drop isn't a review.

curl
# 1. Create version 2 and upload its files to the returned targets
curl https://api.slingdrop.my/v1/drops/$DROP_ID/versions \
  -H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
  -d '{"note":"Bigger headline","files":[{"name":"hero.png","size":501877,"type":"image/png"}]}'

curl -X PUT https://slingdrop.my/api/uploads/$FILE_ID \
  -H "x-upload-token: $UPLOAD_TOKEN" --data-binary @hero.png

# 2. Record the round
curl https://api.slingdrop.my/v1/drops/$DROP_ID/rounds \
  -H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
  -d '{"note":"Bigger headline, tighter line height","resolves":["c_8k2"]}'

A complete loop#

review-loop.ts
import { openAsBlob } from "node:fs";
import { Slingdrop, type ReviewComment } from "@slingdrop/sdk";

const sd = new Slingdrop({ apiKey: process.env.SLINGDROP_API_KEY });

// Your own code: rebuild the file from the reviewer's comments.
declare function applyFeedback(comments: ReviewComment[]): Promise<string>;

const { drop } = await sd.drops.create(
  [{ name: "hero.png", data: await openAsBlob("hero.png") }],
  { title: "Homepage hero", purpose: "feedback" },
);
let state = await sd.reviews.start(drop.id, { agentName: "Release bot" });
console.log("Review it at", state.url);

while (state.status !== "approved") {
  state = await sd.reviews.feedback(drop.id, { since: state.cursor, wait: 60 });
  if (state.status !== "changes_requested") continue; // still waiting

  const open = state.comments.filter((c) => !c.fromAgent && !c.resolved);
  const path = await applyFeedback(open);
  await sd.drops.addVersion(drop.id, [{ name: "hero.png", data: await openAsBlob(path) }]);
  state = await sd.reviews.round(drop.id, { note: "Addressed your comments", resolves: open.map((c) => c.id) });
}
console.log("Approved by", state.decision?.by);

Reply to and resolve comments#

POST/drops/{id}/comments/{cid}/repliesReply as your agent
POST/drops/{id}/comments/{cid}/resolveResolve, or reopen with resolved: false
GET/drops/{id}/commentsEvery comment on the drop, with anchors

Replies ({ "text": "…" }, up to 4,000 characters) are labelled as coming from your agent, e.g. Release bot (AI), and only work on drops you started a review on. Resolving takes { "resolved": true | false } (default true) and works on any drop you own.

curl
curl https://api.slingdrop.my/v1/drops/$DROP_ID/comments/$COMMENT_ID/replies \
  -H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
  -d '{"text":"Do you mean the headline or the subheading?"}'

curl https://api.slingdrop.my/v1/drops/$DROP_ID/comments/$COMMENT_ID/resolve \
  -H "Authorization: Bearer $SLINGDROP_API_KEY" -H "content-type: application/json" \
  -d '{"resolved":true}'

Approvals#

GET/drops/{id}/approvalsAll decisions on a drop, newest first

Each decision belongs to the version that was current when it was made, so an old approval never carries over to a new version. Signers who were signed in to Slingdrop with a confirmed email have signerVerified: true.

curl
curl https://api.slingdrop.my/v1/drops/$DROP_ID/approvals \
  -H "Authorization: Bearer $SLINGDROP_API_KEY"
200 OK
{
  "data": [
    {
      "id": "ap_1",
      "versionId": "v_02",
      "fileId": null,
      "decision": "approved",
      "signerName": "Ana",
      "signerEmail": "ana@studio.com",
      "signerVerified": true,
      "note": "Ship it",
      "createdAt": "2026-10-21T10:02:55.000Z"
    }
  ]
}

Prefer push over polling? Subscribe to comment.created, approval.created and version.created with a webhook.