← Share Sweep
API

Run Share Sweep from your own code

The one metered task, sharing, is available over HTTP. It takes a file-sharing export as the page has read it - who the files are meant for, the organisation's own domains, the totals, one numbered line per outside grant (g1, g2 ...) with the file, whether its name looks sensitive, its folder, the owner, who it reaches and the role, and the outside grants past the list counted by audience - and returns one action per listed grant (revoke, viewer, ask_owner, keep) with a reason for each and, for the owner, a question, a verdict (exposed or contained) and a note.

The division of labour first: the page reads the export, classes every grant against your own domains (inside, an outside person, a personal mailbox, a whole outside domain, anyone with the link, public on the web), marks file names that look sensitive and ranks the outside grants worst first; the run decides what to do with each listed grant, naming grants only by id so the page can tie each action to a real row of the export. The page reads up to 20000 lines and lists up to 25 outside grants; the rest are counted by audience and get no action. After the reply it keeps one known action per listed grant, checks each one against its own rule and the verdict against the grants as they are, and warns when a sentence quotes a number the export does not have, names a grant by its id, or calls anyone he or she. Nothing is changed in Drive - not by the page and not by the run; the plan is a list for you or the file owners to act on. Your code can do the same with the engine files the page loads (see "Build the input yourself").

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"share-sweep"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field (task, goal, domains, totals, grants, others) is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the sharing run is metered and needs a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field, the input and the rules

Every run sends task: sharing (the one task is sharing); a missing or different one is still answered as sharing. All fields are strings. What each holds, in the words of the system prompt, with the first sample's value (its first line where it has several):

FieldRequiredWhat it holds
taskyesAlways sharing. If it is missing or different, still answer this task and set "task": "sharing".
goalyesWhat the files are for and who they are meant to reach, in the person's words, or none. First sample: Marketing team Drive - Brightwave, our agency, edits the campaign files.
domainsyesThe organisation's own domains. First sample: northwind.com.
totalsyesHow many files and grants, how many inside and outside, and the outside grants by kind. First sample: 9 files, 12 grants: 2 inside, 10 outside (1 public on the web, 2 anyone with the link, 6 outside people, 1 personal mailbox).
grantsyesOne line per outside grant: g1. "File name" (sensitive-looking name) in Folder | owner address | who | role. who is an address (marked (personal mailbox) for Gmail, Outlook and the like), everyone at domain (whole domain), anyone with the link or public on the web. role is editor, commenter or viewer. First sample: g1. "Influencer contracts 2026.xlsx" (sensitive-looking name) in Campaigns/Spring | owner maya@northwind.com | ana@brightwave.io | editor ...
othersyesOutside grants past the list, counted by audience, or none. They get no action. First sample: none.
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

How the page reads the export (ShareKit.readReport, called by ShareKit.form): up to 20000 lines. JSON (text that starts with [ or {) is read as a Drive API files list - an array, or an object with files, items or data - where each file has a name (or title), an optional path or folder, owners[0].emailAddress and permissions with type, role, emailAddress, domain and allowFileDiscovery. A table (tab, comma or semicolon separated) is found by its header row: a file column (file, file name, title, name, document, item ...) plus a shared-with column (shared with, grantee, recipient, user, email, member, principal ...), a link-sharing column (a header holding visibility, link sharing, general access or sharing) or GAM-style permissions.N.emailAddress, .role, .type, .domain and .allowFileDiscovery columns; an owner column, a folder or path column, a role column (role, access, permission, can ...), a type column and a domain column are used when there are. Without a header, each line is read as a file (with its folder before the last /), who and a role, split by | or tabs, as in Sales/Budget.xlsx | ana@gmail.com | editor. A role reads as editor (writer, can edit, organizer, contributor ...), commenter or viewer; owner grants are set aside, and a row with no file or no grantee is counted in the warning.

What the page works out, free and before any run: the same file and grantee count once, at the strongest role. Your own domains are the ones given, or, with none, the most common owner domain that is not a personal mailbox (and the input says so). Each grant is classed (ShareKit.form): internal when the grantee's domain is one of yours or under it; personal for Gmail, Outlook, Hotmail, Yahoo, iCloud, Proton, GMX, AOL and similar mailboxes (ShareKit.PERSONAL); outside domain for a whole outside domain; outside for any other outside address; link for anyone with the link; public when the file can be found on the web. A file name or folder looks sensitive when it holds a word such as salary, payroll, contract, NDA, budget, board, confidential, password, invoice, tax, medical, customer list, pricing, financial, cap table, legal, donor or students (ShareKit.SENSITIVE). Every outside grant gets a severity: high for public on the web, or a link, an editor or a commenter on a sensitive-looking name; medium for any other link, outside editor or commenter, personal mailbox, or outside viewer of a sensitive-looking name; low for other outside viewers. The outside grants are sorted by severity, then the stronger role, then folder and file, then grantee, and the first 25 get the ids g1 ... g25; the rest are counted by audience in others and get no action.

The model's rules

As the system prompt states them, in order:

  1. plan - one entry for each line in grants, in id order: grant (its id), action, why (one sentence, at most 20 words, using only that line and goal) and question. A grantee is named when goal contains its address, its domain, or the domain's name without its ending (acme for acme.com); a personal mailbox counts only by its full address. Take the first action that applies:
    • revoke - the grant is public on the web, or it is anyone with the link on a sensitive-looking name. Remove that access.
    • keep - the grantee is named.
    • ask_owner - a personal mailbox, or anyone with the link. The owner is asked whether it is still needed.
    • viewer - an outside editor or commenter. Lower it to view only.
    • ask_owner - anything else.
    For ask_owner, question is one sentence, at most 20 words, to the file's owner; "" for every other action.
  2. verdict - exposed when any listed grant is public on the web, or is anyone with the link, an editor or a commenter on a sensitive-looking name; contained otherwise. Judge the grants as they are now, before the plan.
  3. note - one sentence, at most 30 words: what is exposed (or that nothing sensitive is), and the first change to make - a revoke on a sensitive-looking name first, then any other revoke, then the rest.

And the house rules:

The reply

One JSON object, in the shape the system prompt asks for:

{
  "task": "sharing",
  "verdict": "exposed | contained",
  "plan": [ { "grant": "g1", "action": "revoke | keep | ask_owner | viewer", "why": "one sentence", "question": "one sentence or empty" } ],
  "note": "one sentence"
}

The page reads the reply with ReplyKit.normalize (the verdict, the ids and the actions matched in any case, and an action written as two words or with a hyphen, such as ask owner, read as its id; make viewer and view only read as viewer) and keeps what it trusts with ReplyKit.trusted(reply, built): an entry is set aside, with the reason, when it names a grant the page did not send, a grant that already has an action, an action the page does not know, lowers a link or a public file to view only instead of deciding on it, or lowers a grant that is already view-only; every listed grant with no action is listed as missing. t.acts maps each id to its action, t.why to its reason and t.ask to the question for the owner. The files, the folders, the owners and the grantees always come from the page's own reading, never from the reply.

ReplyKit.check(reply, built) lists, under "The page's check": a missing verdict, or one the grants as they are contradict (ShareKit.expectVerdict: exposed when any outside grant is high severity); every entry set aside, and every listed grant with no action; an action that is not the one the rule gives (ShareKit.expectAction(grant, goal): revoke what is public on the web or a link on a sensitive-looking name; keep what the goal names by address, by domain or by the domain's name without its ending; ask the owner about a personal mailbox or a link; make an outside editor or commenter view-only; ask the owner about anything else); an owner action with no question, or another action that carries one; a number in a sentence that is not in the input; a sentence that names a grant id or an internal field or uses backticks; a sentence that calls anyone he, she, him or her; and words such as "again", "already", "always", "never" or "every" that the input never used. Any warning makes the status flagged; otherwise it is kept. The verdict on the page reads "Something sensitive is exposed" or "Nothing sensitive is exposed".

The exports: ShareKit.toCSV(form, t.acts) writes one row per grant, inside ones included - the listed outside grants first in id order - with the id, the file, the folder, the owner, who it is shared with, the class, the role, whether the name looks sensitive, the severity and the plan, with CRLF line ends; ReplyKit.md and ReplyKit.json write the own domains, the counts and every listed grant with its plan, reason and question; and the flow map (ShareMap.draw, folders on the left, outside audiences on the right, one band per pair) saves as PNG or SVG.

Worked examples - every sample

These are the free samples on the page, one per export shape: the form as filled in, how the page reads the export - the grants, the classes and the outside ones worst first - the exact input it sends, the reply that replays for free, and what the page does with it - the actions it trusts, grant by grant against its own rule, the verdict against the grants as they are, and its own check. Together they cover the verdicts exposed and contained.

A marketing team (GAM export)

The form: your domains northwind.com; who the files are meant for: Marketing team Drive - Brightwave, our agency, edits the campaign files.

The export as pasted:

Owner,id,name,path,permissions.0.emailAddress,permissions.0.role,permissions.0.type,permissions.1.emailAddress,permissions.1.role,permissions.1.type,permissions.2.emailAddress,permissions.2.role,permissions.2.type,permissions.2.allowFileDiscovery
maya@northwind.com,1a,Spring campaign brief.docx,Campaigns/Spring,maya@northwind.com,owner,user,ana@brightwave.io,writer,user,,,,
maya@northwind.com,1b,Spring campaign storyboard.pptx,Campaigns/Spring,maya@northwind.com,owner,user,ana@brightwave.io,writer,user,leo@brightwave.io,commenter,user,
maya@northwind.com,1c,Influencer contracts 2026.xlsx,Campaigns/Spring,maya@northwind.com,owner,user,ana@brightwave.io,writer,user,,reader,anyone,False
tom@northwind.com,2a,Customer list Q3.xlsx,Sales,tom@northwind.com,owner,user,,,,,reader,anyone,True
tom@northwind.com,2b,Pricing sheet 2026.xlsx,Sales,tom@northwind.com,owner,user,jules.martin@gmail.com,writer,user,,,,
tom@northwind.com,2c,Q3 pipeline review.xlsx,Sales,tom@northwind.com,owner,user,sam@northwind.com,writer,user,ops@harbor-logistics.com,writer,user,
tom@northwind.com,3a,Trade show photos,Events,tom@northwind.com,owner,user,,,,,reader,anyone,False
maya@northwind.com,4a,Logo files.zip,Brand,maya@northwind.com,owner,user,print@quickprint.co.uk,reader,user,,,,
maya@northwind.com,4b,Brand guidelines.pdf,Brand,maya@northwind.com,owner,user,team@northwind.com,reader,group,,,,

The page reads 10 lines as a GAM-style file list (one row per file, permissions.N columns): 12 grants on 9 files. Own domain: northwind.com. 2 inside and 10 outside (1 public on the web, 2 anyone with the link, 6 outside people, 1 personal mailbox).

The outside grants (ShareKit.form), worst first, each listed one sent as one line (ShareKit.itemLine):

IdFileFolderOwnerShared withRoleSeverity
g1Influencer contracts 2026.xlsx (sensitive-looking name)Campaigns/Springmaya@northwind.comana@brightwave.ioeditorhigh
g2Pricing sheet 2026.xlsx (sensitive-looking name)Salestom@northwind.comjules.martin@gmail.com (personal mailbox)editorhigh
g3Influencer contracts 2026.xlsx (sensitive-looking name)Campaigns/Springmaya@northwind.comanyone with the linkviewerhigh
g4Customer list Q3.xlsx (sensitive-looking name)Salestom@northwind.compublic on the webviewerhigh
g5Spring campaign brief.docxCampaigns/Springmaya@northwind.comana@brightwave.ioeditormedium
g6Spring campaign storyboard.pptxCampaigns/Springmaya@northwind.comana@brightwave.ioeditormedium
g7Q3 pipeline review.xlsxSalestom@northwind.comops@harbor-logistics.comeditormedium
g8Spring campaign storyboard.pptxCampaigns/Springmaya@northwind.comleo@brightwave.iocommentermedium
g9Trade show photosEventstom@northwind.comanyone with the linkviewermedium
g10Logo files.zipBrandmaya@northwind.comprint@quickprint.co.ukviewerlow

Input:

{
 "task": "sharing",
 "goal": "Marketing team Drive - Brightwave, our agency, edits the campaign files",
 "domains": "northwind.com",
 "totals": "9 files, 12 grants: 2 inside, 10 outside (1 public on the web, 2 anyone with the link, 6 outside people, 1 personal mailbox)",
 "grants": "g1. \"Influencer contracts 2026.xlsx\" (sensitive-looking name) in Campaigns/Spring | owner maya@northwind.com | ana@brightwave.io | editor\ng2. \"Pricing sheet 2026.xlsx\" (sensitive-looking name) in Sales | owner tom@northwind.com | jules.martin@gmail.com (personal mailbox) | editor\ng3. \"Influencer contracts 2026.xlsx\" (sensitive-looking name) in Campaigns/Spring | owner maya@northwind.com | anyone with the link | viewer\ng4. \"Customer list Q3.xlsx\" (sensitive-looking name) in Sales | owner tom@northwind.com | public on the web | viewer\ng5. \"Spring campaign brief.docx\" in Campaigns/Spring | owner maya@northwind.com | ana@brightwave.io | editor\ng6. \"Spring campaign storyboard.pptx\" in Campaigns/Spring | owner maya@northwind.com | ana@brightwave.io | editor\ng7. \"Q3 pipeline review.xlsx\" in Sales | owner tom@northwind.com | ops@harbor-logistics.com | editor\ng8. \"Spring campaign storyboard.pptx\" in Campaigns/Spring | owner maya@northwind.com | leo@brightwave.io | commenter\ng9. \"Trade show photos\" in Events | owner tom@northwind.com | anyone with the link | viewer\ng10. \"Logo files.zip\" in Brand | owner maya@northwind.com | print@quickprint.co.uk | viewer",
 "others": "none"
}

Reply:

{
 "task": "sharing",
 "verdict": "exposed",
 "plan": [
  {
   "grant": "g1",
   "action": "keep",
   "why": "ana@brightwave.io is named as the agency for the campaign files, so access on Influencer contracts 2026.xlsx continues.",
   "question": ""
  },
  {
   "grant": "g2",
   "action": "ask_owner",
   "why": "jules.martin@gmail.com is a personal mailbox not named for Pricing sheet 2026.xlsx, so the owner should confirm.",
   "question": "Is jules.martin@gmail.com's editor access to Pricing sheet 2026.xlsx still needed?"
  },
  {
   "grant": "g3",
   "action": "revoke",
   "why": "Anyone with the link can view the sensitive-looking Influencer contracts 2026.xlsx, so that access is removed.",
   "question": ""
  },
  {
   "grant": "g4",
   "action": "revoke",
   "why": "Customer list Q3.xlsx is sensitive-looking and public on the web, so that access is removed.",
   "question": ""
  },
  {
   "grant": "g5",
   "action": "keep",
   "why": "ana@brightwave.io is named as the agency for the campaign files, so access on Spring campaign brief.docx continues.",
   "question": ""
  },
  {
   "grant": "g6",
   "action": "keep",
   "why": "ana@brightwave.io is named as the agency for the campaign files, so access on Spring campaign storyboard.pptx continues.",
   "question": ""
  },
  {
   "grant": "g7",
   "action": "viewer",
   "why": "ops@harbor-logistics.com is an outside editor not named for Q3 pipeline review.xlsx, so access is lowered to view only.",
   "question": ""
  },
  {
   "grant": "g8",
   "action": "keep",
   "why": "leo@brightwave.io is named as the agency for the campaign files, so access on Spring campaign storyboard.pptx continues.",
   "question": ""
  },
  {
   "grant": "g9",
   "action": "ask_owner",
   "why": "Anyone with the link can view the Trade show photos, so the owner should confirm whether that access is needed.",
   "question": "Is the link-shared access to the Trade show photos still needed?"
  },
  {
   "grant": "g10",
   "action": "ask_owner",
   "why": "print@quickprint.co.uk is an outside viewer not named for Logo files.zip, so the owner should confirm.",
   "question": "Is print@quickprint.co.uk's viewer access to Logo files.zip still needed?"
  }
 ],
 "note": "Influencer contracts 2026.xlsx is shared with anyone with the link, and Customer list Q3.xlsx is public on the web; remove the link access on Influencer contracts 2026.xlsx first."
}

The actions the page trusts (ReplyKit.trusted), grant by grant, next to the one its own rule gives (ShareKit.expectAction):

GrantShared withRoleActionThe ruleWhy
Influencer contracts 2026.xlsx (g1)ana@brightwave.ioeditorkeepkeepana@brightwave.io is named as the agency for the campaign files, so access on Influencer contracts 2026.xlsx continues.
Pricing sheet 2026.xlsx (g2)jules.martin@gmail.com (personal mailbox)editorask_owner (ask the owner)ask_owner (ask the owner)jules.martin@gmail.com is a personal mailbox not named for Pricing sheet 2026.xlsx, so the owner should confirm. Ask: "Is jules.martin@gmail.com's editor access to Pricing sheet 2026.xlsx still needed?"
Influencer contracts 2026.xlsx (g3)anyone with the linkviewerrevokerevokeAnyone with the link can view the sensitive-looking Influencer contracts 2026.xlsx, so that access is removed.
Customer list Q3.xlsx (g4)public on the webviewerrevokerevokeCustomer list Q3.xlsx is sensitive-looking and public on the web, so that access is removed.
Spring campaign brief.docx (g5)ana@brightwave.ioeditorkeepkeepana@brightwave.io is named as the agency for the campaign files, so access on Spring campaign brief.docx continues.
Spring campaign storyboard.pptx (g6)ana@brightwave.ioeditorkeepkeepana@brightwave.io is named as the agency for the campaign files, so access on Spring campaign storyboard.pptx continues.
Q3 pipeline review.xlsx (g7)ops@harbor-logistics.comeditorviewer (make view-only)viewer (make view-only)ops@harbor-logistics.com is an outside editor not named for Q3 pipeline review.xlsx, so access is lowered to view only.
Spring campaign storyboard.pptx (g8)leo@brightwave.iocommenterkeepkeepleo@brightwave.io is named as the agency for the campaign files, so access on Spring campaign storyboard.pptx continues.
Trade show photos (g9)anyone with the linkviewerask_owner (ask the owner)ask_owner (ask the owner)Anyone with the link can view the Trade show photos, so the owner should confirm whether that access is needed. Ask: "Is the link-shared access to the Trade show photos still needed?"
Logo files.zip (g10)print@quickprint.co.ukviewerask_owner (ask the owner)ask_owner (ask the owner)print@quickprint.co.uk is an outside viewer not named for Logo files.zip, so the owner should confirm. Ask: "Is print@quickprint.co.uk's viewer access to Logo files.zip still needed?"

The verdict: exposed - "Something sensitive is exposed"; it agrees with the grants as they are.

The note: "Influencer contracts 2026.xlsx is shared with anyone with the link, and Customer list Q3.xlsx is public on the web; remove the link access on Influencer contracts 2026.xlsx first."

The page's check (ReplyKit.check): kept.

No notes.

A seed-round data room (Drive JSON)

The form: your domains fernhill.ai; who the files are meant for: Seed round data room - Calder & Pine (calderpine.com) are our lawyers.

The export as pasted:

{
 "files": [
  {
   "name": "Cap table v7.xlsx",
   "path": "Data room/Finance",
   "owners": [
    {
     "emailAddress": "fern@fernhill.ai"
    }
   ],
   "permissions": [
    {
     "type": "user",
     "role": "owner",
     "emailAddress": "fern@fernhill.ai"
    },
    {
     "type": "anyone",
     "role": "reader",
     "allowFileDiscovery": false
    },
    {
     "type": "user",
     "role": "writer",
     "emailAddress": "kai@calderpine.com"
    }
   ]
  },
  {
   "name": "Board deck March.pdf",
   "path": "Data room/Board",
   "owners": [
    {
     "emailAddress": "fern@fernhill.ai"
    }
   ],
   "permissions": [
    {
     "type": "user",
     "role": "owner",
     "emailAddress": "fern@fernhill.ai"
    },
    {
     "type": "user",
     "role": "reader",
     "emailAddress": "priya.investor@gmail.com"
    },
    {
     "type": "user",
     "role": "writer",
     "emailAddress": "sam@fernhill.ai"
    }
   ]
  },
  {
   "name": "Product roadmap.pdf",
   "path": "Data room/Product",
   "owners": [
    {
     "emailAddress": "sam@fernhill.ai"
    }
   ],
   "permissions": [
    {
     "type": "user",
     "role": "owner",
     "emailAddress": "sam@fernhill.ai"
    },
    {
     "type": "anyone",
     "role": "reader",
     "allowFileDiscovery": false
    }
   ]
  },
  {
   "name": "Shareholder agreement draft.docx",
   "path": "Data room/Legal",
   "owners": [
    {
     "emailAddress": "fern@fernhill.ai"
    }
   ],
   "permissions": [
    {
     "type": "user",
     "role": "owner",
     "emailAddress": "fern@fernhill.ai"
    },
    {
     "type": "user",
     "role": "writer",
     "emailAddress": "kai@calderpine.com"
    },
    {
     "type": "domain",
     "role": "reader",
     "domain": "calderpine.com"
    }
   ]
  },
  {
   "name": "Hiring plan.xlsx",
   "path": "Ops",
   "owners": [
    {
     "emailAddress": "sam@fernhill.ai"
    }
   ],
   "permissions": [
    {
     "type": "user",
     "role": "owner",
     "emailAddress": "sam@fernhill.ai"
    },
    {
     "type": "user",
     "role": "writer",
     "emailAddress": "dev@pixelforge.dev"
    },
    {
     "type": "user",
     "role": "reader",
     "emailAddress": "fern@fernhill.ai"
    }
   ]
  },
  {
   "name": "Team offsite photos",
   "path": "Ops",
   "owners": [
    {
     "emailAddress": "sam@fernhill.ai"
    }
   ],
   "permissions": [
    {
     "type": "user",
     "role": "owner",
     "emailAddress": "sam@fernhill.ai"
    },
    {
     "type": "anyone",
     "role": "reader",
     "allowFileDiscovery": true
    }
   ]
  }
 ]
}

The page reads 150 lines as a Drive API JSON files list with permissions: 10 grants on 6 files. Own domain: fernhill.ai. 2 inside and 8 outside (1 public on the web, 2 anyone with the link, 1 whole outside domain, 3 outside people, 1 personal mailbox).

The outside grants (ShareKit.form), worst first, each listed one sent as one line (ShareKit.itemLine):

IdFileFolderOwnerShared withRoleSeverity
g1Cap table v7.xlsx (sensitive-looking name)Data room/Financefern@fernhill.aikai@calderpine.comeditorhigh
g2Shareholder agreement draft.docx (sensitive-looking name)Data room/Legalfern@fernhill.aikai@calderpine.comeditorhigh
g3Cap table v7.xlsx (sensitive-looking name)Data room/Financefern@fernhill.aianyone with the linkviewerhigh
g4Team offsite photosOpssam@fernhill.aipublic on the webviewerhigh
g5Hiring plan.xlsxOpssam@fernhill.aidev@pixelforge.deveditormedium
g6Board deck March.pdf (sensitive-looking name)Data room/Boardfern@fernhill.aipriya.investor@gmail.com (personal mailbox)viewermedium
g7Shareholder agreement draft.docx (sensitive-looking name)Data room/Legalfern@fernhill.aieveryone at calderpine.com (whole domain)viewermedium
g8Product roadmap.pdfData room/Productsam@fernhill.aianyone with the linkviewermedium

Input:

{
 "task": "sharing",
 "goal": "Seed round data room - Calder & Pine (calderpine.com) are our lawyers",
 "domains": "fernhill.ai",
 "totals": "6 files, 10 grants: 2 inside, 8 outside (1 public on the web, 2 anyone with the link, 1 whole outside domain, 3 outside people, 1 personal mailbox)",
 "grants": "g1. \"Cap table v7.xlsx\" (sensitive-looking name) in Data room/Finance | owner fern@fernhill.ai | kai@calderpine.com | editor\ng2. \"Shareholder agreement draft.docx\" (sensitive-looking name) in Data room/Legal | owner fern@fernhill.ai | kai@calderpine.com | editor\ng3. \"Cap table v7.xlsx\" (sensitive-looking name) in Data room/Finance | owner fern@fernhill.ai | anyone with the link | viewer\ng4. \"Team offsite photos\" in Ops | owner sam@fernhill.ai | public on the web | viewer\ng5. \"Hiring plan.xlsx\" in Ops | owner sam@fernhill.ai | dev@pixelforge.dev | editor\ng6. \"Board deck March.pdf\" (sensitive-looking name) in Data room/Board | owner fern@fernhill.ai | priya.investor@gmail.com (personal mailbox) | viewer\ng7. \"Shareholder agreement draft.docx\" (sensitive-looking name) in Data room/Legal | owner fern@fernhill.ai | everyone at calderpine.com (whole domain) | viewer\ng8. \"Product roadmap.pdf\" in Data room/Product | owner sam@fernhill.ai | anyone with the link | viewer",
 "others": "none"
}

Reply:

{
 "task": "sharing",
 "verdict": "exposed",
 "plan": [
  {
   "grant": "g1",
   "action": "keep",
   "why": "kai@calderpine.com is named as the lawyers for the data room, so access on Cap table v7.xlsx continues.",
   "question": ""
  },
  {
   "grant": "g2",
   "action": "keep",
   "why": "kai@calderpine.com is named as the lawyers for the data room, so access on Shareholder agreement draft.docx continues.",
   "question": ""
  },
  {
   "grant": "g3",
   "action": "revoke",
   "why": "Anyone with the link can view the sensitive-looking Cap table v7.xlsx, so that access is removed.",
   "question": ""
  },
  {
   "grant": "g4",
   "action": "revoke",
   "why": "Team offsite photos is public on the web, so that access is removed.",
   "question": ""
  },
  {
   "grant": "g5",
   "action": "viewer",
   "why": "dev@pixelforge.dev is an outside editor not named for Hiring plan.xlsx, so access is lowered to view only.",
   "question": ""
  },
  {
   "grant": "g6",
   "action": "ask_owner",
   "why": "priya.investor@gmail.com is a personal mailbox not named for the sensitive-looking Board deck March.pdf, so the owner should confirm.",
   "question": "Is priya.investor@gmail.com's viewer access to Board deck March.pdf still needed?"
  },
  {
   "grant": "g7",
   "action": "keep",
   "why": "everyone at calderpine.com is named as the lawyers for the data room, so access on Shareholder agreement draft.docx continues.",
   "question": ""
  },
  {
   "grant": "g8",
   "action": "ask_owner",
   "why": "Anyone with the link can view Product roadmap.pdf, so the owner should confirm whether that access is needed.",
   "question": "Is the link-shared access to Product roadmap.pdf still needed?"
  }
 ],
 "note": "Cap table v7.xlsx is shared with anyone with the link and Team offsite photos is public on the web; remove the link access on Cap table v7.xlsx first."
}

The actions the page trusts (ReplyKit.trusted), grant by grant, next to the one its own rule gives (ShareKit.expectAction):

GrantShared withRoleActionThe ruleWhy
Cap table v7.xlsx (g1)kai@calderpine.comeditorkeepkeepkai@calderpine.com is named as the lawyers for the data room, so access on Cap table v7.xlsx continues.
Shareholder agreement draft.docx (g2)kai@calderpine.comeditorkeepkeepkai@calderpine.com is named as the lawyers for the data room, so access on Shareholder agreement draft.docx continues.
Cap table v7.xlsx (g3)anyone with the linkviewerrevokerevokeAnyone with the link can view the sensitive-looking Cap table v7.xlsx, so that access is removed.
Team offsite photos (g4)public on the webviewerrevokerevokeTeam offsite photos is public on the web, so that access is removed.
Hiring plan.xlsx (g5)dev@pixelforge.deveditorviewer (make view-only)viewer (make view-only)dev@pixelforge.dev is an outside editor not named for Hiring plan.xlsx, so access is lowered to view only.
Board deck March.pdf (g6)priya.investor@gmail.com (personal mailbox)viewerask_owner (ask the owner)ask_owner (ask the owner)priya.investor@gmail.com is a personal mailbox not named for the sensitive-looking Board deck March.pdf, so the owner should confirm. Ask: "Is priya.investor@gmail.com's viewer access to Board deck March.pdf still needed?"
Shareholder agreement draft.docx (g7)everyone at calderpine.com (whole domain)viewerkeepkeepeveryone at calderpine.com is named as the lawyers for the data room, so access on Shareholder agreement draft.docx continues.
Product roadmap.pdf (g8)anyone with the linkviewerask_owner (ask the owner)ask_owner (ask the owner)Anyone with the link can view Product roadmap.pdf, so the owner should confirm whether that access is needed. Ask: "Is the link-shared access to Product roadmap.pdf still needed?"

The verdict: exposed - "Something sensitive is exposed"; it agrees with the grants as they are.

The note: "Cap table v7.xlsx is shared with anyone with the link and Team offsite photos is public on the web; remove the link access on Cap table v7.xlsx first."

The page's check (ReplyKit.check): kept.

No notes.

A volunteer program (CSV)

The form: your domains riverbend.org; who the files are meant for: Volunteer program folder - the county library (bclibrary.org) co-runs the reading program.

The export as pasted:

File,Folder,Owner,Shared with,Role
Volunteer schedule fall.xlsx,Programs/Reading,jo@riverbend.org,coord@bclibrary.org,Editor
Volunteer schedule fall.xlsx,Programs/Reading,jo@riverbend.org,kim@riverbend.org,Editor
Reading list grade 3.docx,Programs/Reading,jo@riverbend.org,bclibrary.org,Viewer
Event flyer.pdf,Outreach,jo@riverbend.org,printshop@inkwell-print.com,Viewer
Newsletter draft.docx,Outreach,jo@riverbend.org,mark.v@outlook.com,Commenter
Grant report 2025.docx,Admin,dee@riverbend.org,reviewer@foundation-trust.org,Commenter
Donor thank-you letters.docx,Admin,dee@riverbend.org,intern.sam@gmail.com,Viewer
Donor thank-you letters.docx,Admin,dee@riverbend.org,jo@riverbend.org,Editor

The page reads 9 lines as a comma-separated sharing report with a header row: 8 grants on 6 files. Own domain: riverbend.org. 2 inside and 6 outside (1 whole outside domain, 3 outside people, 2 personal mailboxes).

The outside grants (ShareKit.form), worst first, each listed one sent as one line (ShareKit.itemLine):

IdFileFolderOwnerShared withRoleSeverity
g1Volunteer schedule fall.xlsxPrograms/Readingjo@riverbend.orgcoord@bclibrary.orgeditormedium
g2Grant report 2025.docxAdmindee@riverbend.orgreviewer@foundation-trust.orgcommentermedium
g3Newsletter draft.docxOutreachjo@riverbend.orgmark.v@outlook.com (personal mailbox)commentermedium
g4Donor thank-you letters.docx (sensitive-looking name)Admindee@riverbend.orgintern.sam@gmail.com (personal mailbox)viewermedium
g5Event flyer.pdfOutreachjo@riverbend.orgprintshop@inkwell-print.comviewerlow
g6Reading list grade 3.docxPrograms/Readingjo@riverbend.orgeveryone at bclibrary.org (whole domain)viewerlow

Input:

{
 "task": "sharing",
 "goal": "Volunteer program folder - the county library (bclibrary.org) co-runs the reading program",
 "domains": "riverbend.org",
 "totals": "6 files, 8 grants: 2 inside, 6 outside (1 whole outside domain, 3 outside people, 2 personal mailboxes)",
 "grants": "g1. \"Volunteer schedule fall.xlsx\" in Programs/Reading | owner jo@riverbend.org | coord@bclibrary.org | editor\ng2. \"Grant report 2025.docx\" in Admin | owner dee@riverbend.org | reviewer@foundation-trust.org | commenter\ng3. \"Newsletter draft.docx\" in Outreach | owner jo@riverbend.org | mark.v@outlook.com (personal mailbox) | commenter\ng4. \"Donor thank-you letters.docx\" (sensitive-looking name) in Admin | owner dee@riverbend.org | intern.sam@gmail.com (personal mailbox) | viewer\ng5. \"Event flyer.pdf\" in Outreach | owner jo@riverbend.org | printshop@inkwell-print.com | viewer\ng6. \"Reading list grade 3.docx\" in Programs/Reading | owner jo@riverbend.org | everyone at bclibrary.org (whole domain) | viewer",
 "others": "none"
}

Reply:

{
 "task": "sharing",
 "verdict": "contained",
 "plan": [
  {
   "grant": "g1",
   "action": "keep",
   "why": "coord@bclibrary.org is named as the county library that co-runs the reading program, so access on Volunteer schedule fall.xlsx continues.",
   "question": ""
  },
  {
   "grant": "g2",
   "action": "viewer",
   "why": "reviewer@foundation-trust.org is an outside commenter not named for Grant report 2025.docx, so access is lowered to view only.",
   "question": ""
  },
  {
   "grant": "g3",
   "action": "ask_owner",
   "why": "mark.v@outlook.com is a personal mailbox not named for Newsletter draft.docx, so the owner should confirm.",
   "question": "Is mark.v@outlook.com's commenter access to Newsletter draft.docx still needed?"
  },
  {
   "grant": "g4",
   "action": "ask_owner",
   "why": "intern.sam@gmail.com is a personal mailbox not named for the sensitive-looking Donor thank-you letters.docx, so the owner should confirm.",
   "question": "Is intern.sam@gmail.com's viewer access to Donor thank-you letters.docx still needed?"
  },
  {
   "grant": "g5",
   "action": "ask_owner",
   "why": "printshop@inkwell-print.com is an outside viewer not named for Event flyer.pdf, so the owner should confirm.",
   "question": "Is printshop@inkwell-print.com's viewer access to Event flyer.pdf still needed?"
  },
  {
   "grant": "g6",
   "action": "keep",
   "why": "everyone at bclibrary.org is named as the library co-running the reading program, so access on Reading list grade 3.docx continues.",
   "question": ""
  }
 ],
 "note": "Nothing sensitive is exposed right now, so the first change is lowering reviewer@foundation-trust.org's access on Grant report 2025.docx to view-only."
}

The actions the page trusts (ReplyKit.trusted), grant by grant, next to the one its own rule gives (ShareKit.expectAction):

GrantShared withRoleActionThe ruleWhy
Volunteer schedule fall.xlsx (g1)coord@bclibrary.orgeditorkeepkeepcoord@bclibrary.org is named as the county library that co-runs the reading program, so access on Volunteer schedule fall.xlsx continues.
Grant report 2025.docx (g2)reviewer@foundation-trust.orgcommenterviewer (make view-only)viewer (make view-only)reviewer@foundation-trust.org is an outside commenter not named for Grant report 2025.docx, so access is lowered to view only.
Newsletter draft.docx (g3)mark.v@outlook.com (personal mailbox)commenterask_owner (ask the owner)ask_owner (ask the owner)mark.v@outlook.com is a personal mailbox not named for Newsletter draft.docx, so the owner should confirm. Ask: "Is mark.v@outlook.com's commenter access to Newsletter draft.docx still needed?"
Donor thank-you letters.docx (g4)intern.sam@gmail.com (personal mailbox)viewerask_owner (ask the owner)ask_owner (ask the owner)intern.sam@gmail.com is a personal mailbox not named for the sensitive-looking Donor thank-you letters.docx, so the owner should confirm. Ask: "Is intern.sam@gmail.com's viewer access to Donor thank-you letters.docx still needed?"
Event flyer.pdf (g5)printshop@inkwell-print.comviewerask_owner (ask the owner)ask_owner (ask the owner)printshop@inkwell-print.com is an outside viewer not named for Event flyer.pdf, so the owner should confirm. Ask: "Is printshop@inkwell-print.com's viewer access to Event flyer.pdf still needed?"
Reading list grade 3.docx (g6)everyone at bclibrary.org (whole domain)viewerkeepkeepeveryone at bclibrary.org is named as the library co-running the reading program, so access on Reading list grade 3.docx continues.

The verdict: contained - "Nothing sensitive is exposed"; it agrees with the grants as they are.

The note: "Nothing sensitive is exposed right now, so the first change is lowering reviewer@foundation-trust.org's access on Grant report 2025.docx to view-only."

The page's check (ReplyKit.check): kept.

No notes.

The CSV for the first sample

What ShareKit.toCSV(form, t.acts) writes for "A marketing team (GAM export)" above: one row per grant, the listed outside grants first in id order, then the inside ones, with the class, the severity and the trusted plan (lines end in CRLF). An inside grant has no severity and no plan.

id,file,folder,owner,shared_with,class,role,sensitive_name,severity,plan
g1,Influencer contracts 2026.xlsx,Campaigns/Spring,maya@northwind.com,ana@brightwave.io,outside,editor,yes,high,keep
g2,Pricing sheet 2026.xlsx,Sales,tom@northwind.com,jules.martin@gmail.com (personal mailbox),personal,editor,yes,high,ask owner
g3,Influencer contracts 2026.xlsx,Campaigns/Spring,maya@northwind.com,anyone with the link,link,viewer,yes,high,revoke
g4,Customer list Q3.xlsx,Sales,tom@northwind.com,public on the web,public,viewer,yes,high,revoke
g5,Spring campaign brief.docx,Campaigns/Spring,maya@northwind.com,ana@brightwave.io,outside,editor,,medium,keep
g6,Spring campaign storyboard.pptx,Campaigns/Spring,maya@northwind.com,ana@brightwave.io,outside,editor,,medium,keep
g7,Q3 pipeline review.xlsx,Sales,tom@northwind.com,ops@harbor-logistics.com,outside,editor,,medium,viewer
g8,Spring campaign storyboard.pptx,Campaigns/Spring,maya@northwind.com,leo@brightwave.io,outside,commenter,,medium,keep
g9,Trade show photos,Events,tom@northwind.com,anyone with the link,link,viewer,,medium,ask owner
g10,Logo files.zip,Brand,maya@northwind.com,print@quickprint.co.uk,outside,viewer,,low,ask owner
,Q3 pipeline review.xlsx,Sales,tom@northwind.com,sam@northwind.com,internal,editor,,,
,Brand guidelines.pdf,Brand,maya@northwind.com,team@northwind.com (group),internal,viewer,,,

Build the input yourself

Load the engine in node (global.window = global; require("./sharekit.js"); require("./replykit.js"); require("./flowmap.js")), then:

const K = ShareKit, R = ReplyKit;
const form = K.form({ domains: "northwind.com",
  goal: "Brightwave, our agency, edits the campaign files",
  report: "Campaigns/Spring brief.docx | ana@brightwave.io | editor\nSales/Customer list.xlsx | anyone with the link | viewer\nBudget 2026.xlsx | jules.martin@gmail.com | editor" });
// form.grants: every grant [{file, path, owner, kind, who, domain, role, cls, sensitive, severity, id}]
// form.outside: the outside ones worst first; form.items: the first 25 (g1 ...); form.rest: the ones past the list
K.expectVerdict(form); K.expectAction(form.items[0], form.goal)   // the page's own verdict and rule, no run needed
const built = K.buildInput(form);     // built.ok is false, with built.note, until an outside grant is read
// built.items: the listed grants; built.warn: what the page warns about
const wire = K.toWire(built.input);   // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output));
// const t = R.trusted(reply, built)       // {acts: {g1: action}, why, ask, dropped, missing}
// R.check(reply, built)                    // {status: kept or flagged, notes}
// K.toCSV(form, t.acts)                    // every grant with its class, severity and plan
// ShareMap.draw(form, t.acts, {dark: false})   // the flow map as an SVG string

Form keys: report (the pasted sharing export, any shape above), domains (your own domains, separated by commas or spaces; empty means the most common owner domain) and goal (who the files are meant for, or empty). Nothing here touches Drive: the engine only reads the text you give it.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://share-sweep.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"share-sweep"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "sharing", "goal": "Brightwave, our agency, edits the campaign files", "domains": "northwind.com", "totals": "3 files, 3 grants: 0 inside, 3 outside (1 anyone with the link, 1 outside person, 1 personal mailbox)", "grants": "g1. \"Budget 2026.xlsx\" (sensitive-looking name) in no folder | jules.martin@gmail.com (personal mailbox) | editor\ng2. \"Customer list.xlsx\" (sensitive-looking name) in Sales | anyone with the link | viewer\ng3. \"Spring brief.docx\" in Campaigns | ana@brightwave.io | editor", "others": "none"}
# - see the field table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses share-sweep:<task>:<inputHash>:a<attempt> (the one task is sharing), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="share-sweep:sharing:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.verdict, .note, (.plan[] | "\(.grant) \(.action)")' reply.json

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: share-sweep:sharing:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json