Publish sheets from your agent

Send files through the API, collect replies, and export them for the next version.

Manage agent access

One sheet, every retained version

  1. Give your agent access

    Link an agent with the pairing code it shows you, or create an API key in Agent access. Choose its permissions; a key lasts up to 365 days, or until you revoke it. Copy a key once into your secret manager.

  2. Plan, upload, finalize

    Send the file names, media types, sizes, and SHA-256 hashes to create a sheet. Upload to the returned paths, then finalize after every file succeeds.

  3. Bring replies back

    Fetch a JSON or Markdown export. Send updated files to the same sheet’s versions endpoint.

Read sheets with a scoped key
curl https://tacksheet.com/api/v1/reviews \
  -H 'Authorization: Bearer YOUR_API_KEY'

# Export recorded replies
curl 'https://tacksheet.com/api/v1/reviews/REVIEW_ID/export?format=md' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Replace placeholders locally. Never commit the key or paste it into shared prompts.

A predictable publishing path

Retries have an identity.
Use an Idempotency-Key on each upload plan. Retry the same request with the same key after an uncertain response.
Sharing is deliberate.
A new sheet starts with the shared link off. A key needs sharing permission to create a reviewer link.
Access has an owner.
Revoke a key or a linked agent at any time. A sheet stays until you delete it, or until the expiry you chose.

Publish your first sheet

The standalone client needs Python 3.10 or later. No repository access or Python packages are required.

Create an API key in Agent access with the permissions “Read sheets and export replies” and “Create and update sheets”. Keep the key in your secret manager. The client reads TACKSHEET_API_KEY from the environment; never put it in a command argument or source file.

PowerShell 7
Invoke-WebRequest https://tacksheet.com/downloads/tacksheet.py -OutFile ./tacksheet.py
$env:TACKSHEET_API_KEY = Read-Host "Tacksheet API key" -MaskInput
python ./tacksheet.py publish --title "Homepage review" --feedback ./index.html

# After editing the file
python ./tacksheet.py update REVIEW_ID ./index.html

# Bring replies back to your agent
python ./tacksheet.py export REVIEW_ID --format md --output ./feedback.md
Bash on macOS or Linux
curl --fail --output tacksheet.py https://tacksheet.com/downloads/tacksheet.py
read -rsp 'Tacksheet API key: ' TACKSHEET_API_KEY
export TACKSHEET_API_KEY
python3 ./tacksheet.py publish --title "Homepage review" --feedback ./index.html

# After editing the file
python3 ./tacksheet.py update REVIEW_ID ./index.html

# Bring replies back to your agent
python3 ./tacksheet.py export REVIEW_ID --format md --output ./feedback.md

Recover an interrupted upload

The client prints a private manifest path before its first request. Keep the source files unchanged, then run python ./tacksheet.py resume "MANIFEST_PATH" using that path. It reuses the request identity and checks whether finalization already succeeded. Manifests contain local file paths and upload metadata, never the API key or file contents.

New sheets start with sharing off and are kept until you delete them. Open the workspace URL in the result to inspect your files and create a shared link. An update keeps the same sheet and its retention. If another version was published first, inspect it before starting a fresh update.

Use python ./tacksheet.py --help for the command reference. Plan, workspace and storage limits remain server-enforced.

The versioned API contract

Use Authorization: Bearer YOUR_API_KEY with https://tacksheet.com/api/v1. Keys need reviews:write to publish and reviews:read to read or export. Creating a shared link requires both reviews:write and reviews:share.

1. Plan the files

Compute each file’s SHA-256 hash from its exact bytes. POST JSON to /api/v1/reviews with Content-Type: application/json and a new Idempotency-Key of 8–100 letters, digits, underscores, periods, colons, or hyphens.

{
  "title": "Homepage review",
  "feedbackEnabled": true,
  "files": [{
    "name": "index.html",
    "type": "text/html",
    "size": 1234,
    "sha256": "REPLACE_WITH_64_HEXADECIMAL_SHA256_CHARACTERS"
  }]
}

title is 1–160 characters. A sheet is kept until you delete it; add "expiresInDays" of 1–365 to make it expire. Each version holds up to your plan’s files per version (5 on Free, 15 on Plus, 30 on Pro), each up to your plan’s largest file (10 MB on Free, 250 MB on Plus, 5 GB on Pro). Add "interactive": true to an HTML file of up to 90 MiB to run its scripts in an isolated sandbox. Use each file’s actual byte length and one of the media types listed below.

The response contains review, version, uploads, and finalizeUrl. Each upload gives its id, relative url, method: PUT, byte size, sha256, and whether it is already uploaded. A file larger than 90 MiB also has multipart: partBytes, partCount, partUrl, completeUrl and the uploadedParts already verified. Match files by size and hash; the returned order need not match the request.

2. Upload, then finalize

PUT the raw bytes to every returned upload URL using the same API key and the declared media type. Only accept URLs on https://tacksheet.com under /api/v1/reviews/; do not follow redirects. After every upload succeeds, POST {} to finalizeUrl. A successful response returns the published sheet and version. Until then, the previous published version stays current.

Large files go up in parts

A file with multipart takes no single PUT. Cut it into parts of partBytes (the last part is shorter). For part N from 1 to partCount, PUT its bytes to partUrl with {partNumber} replaced by N, an exact Content-Length, and X-Part-Sha256 set to the part’s lowercase hex SHA-256. Send part 1 first; later parts may go up to three at a time. Then POST {} to completeUrl; finalize also completes a file whose parts are all in. Everything must finish within the plan’s hour.

The server hashes each part as it arrives and refuses one whose bytes do not match its digest (422 part_hash_mismatch), so neither storage copy keeps it. Sending a verified part again is acknowledged; different bytes for it are refused (409 part_conflict). It verifies each part’s size and SHA-256 and stores those digests. The whole-file SHA-256 you planned is recorded and served as you declared it; the server does not recompute it, so check it before you plan. Files of 90 MiB or less keep the single PUT, and their whole-file hash is verified on upload.

3. Retry without creating a duplicate

After an uncertain response, resend the exact plan body with the same idempotency key. Skip files marked uploaded and parts listed in uploadedParts, then retry finalization. Repeating a successful finalize does not make an older version current again. Plans expire after one hour.

4. Publish the next version

POST {"files":[...]} to /api/v1/reviews/REVIEW_ID/versions with a new idempotency key. Optionally include baseVersionId from the sheet you inspected. A 409 publication_conflict means another version became current: fetch the sheet, compare the files, then explicitly start a new plan and key. Retrying the stale plan will not replace the current version.

Read and export

GET /api/v1/reviews/REVIEW_ID returns the sheet, up to 20 retained versions, current assets, and the first page of replies. GET /api/v1/reviews/REVIEW_ID/feedback?cursor=NEXT_CURSOR for more replies. GET /api/v1/reviews/REVIEW_ID/export?format=md (or json or html) for an export; add version=all for every retained version and content=embed to include text files and small images. File downloads accept Range requests and answer 206, so large video and archives stream.

Organise sheets into workspaces

A workspace groups sheets by project, client or bucket; a category says what kind of sheet it is. A sheet is in at most one workspace, or unfiled. Workspaces are free on every plan, up to 50 per account, with names of 1–60 characters that are unique in any case. They are not your account: the account is the owner of every sheet, key and agent.

  • GET /api/v1/workspaces lists them in order with sheetCount.
  • POST /api/v1/workspaces with {"name":"Acme Co"} and an Idempotency-Key creates one, or returns the workspace that already has that name.
  • PATCH /api/v1/workspaces/ID renames one; POST /api/v1/workspaces/order with {"ids":[…]} sets the order of all of them; DELETE /api/v1/workspaces/ID deletes one and leaves its sheets unfiled.

Every sheet carries "workspace": {"id","name"}, or null when unfiled. File a sheet by adding "workspace": {"id":"…"} or {"name":"Acme Co"} to the create plan or to PATCH /api/v1/reviews/REVIEW_ID; null unfiles it, and a new version never moves it. Names resolve exactly, in any case, and never create a workspace. List one workspace with GET /api/v1/reviews?workspace=ID, or workspace=unfiled; it combines with every other filter. Keys need reviews:write to change workspaces; linked agents need workspaces:manage, and only an agent that covers all sheets can rename, reorder or delete them.

Handle errors

Errors return JSON with an error code. 401 requires a valid key; 403 means insufficient access; 402 blocks paid writes, including plan_storage_exceeded; 409 requires inspecting the conflict; 410 means the plan expired; 413 exceeds a size limit; 429 is a request or account limit; 503 is temporary unavailability. Retry uncertain and transient failures with the original request identity and bounded backoff. Never treat a timeout as confirmed publication.

Accepted media types: text/html, text/plain, text/markdown, text/csv, application/json, application/pdf, image/png, image/jpeg, image/webp, image/gif, image/avif, image/svg+xml, video/mp4, video/webm, audio/mpeg, audio/ogg, audio/wav, application/zip, model/gltf-binary, and model/gltf+json. Plan limits apply to storage, active sheets, versions per sheet and files per version.

The sheet kit

Interactive sheets can load the Tacksheet sheet kit: themes, layout, navigation, galleries, video, charts, reactive controls, reply widgets and 3D models, written as plain HTML elements. Each kit version lives at its own address and never changes, so a sheet keeps working as the kit grows.

A sheet that uses the kit
<link rel="stylesheet"
  href="https://tacksheet.com/kit/1.8.0/tacksheet.css">
<script defer
  src="https://tacksheet.com/kit/1.8.0/tacksheet.js"></script>

<tk-page>
  <tk-tape-title level="1" tape="kraft">
    Checkout drop-off
  </tk-tape-title>
  <tk-chart type="bar" data="#steps"
    x="step" y="left"></tk-chart>
  <tk-question type="choice" name="next">
    Which step first?
  </tk-question>
</tk-page>
  • Upload the file with "interactive": true. Its scripts run in a sandbox with no network, so everything a sheet needs ships in the file, the kit, or other files in the same version (./files/NAME).
  • Reply widgets send answers through Tacksheet’s own page, after the reader confirms in its Send box. A free-text reply holds up to 4,000 characters; a longer one is refused, never cut. Outside Tacksheet they stay disabled.
  • Pin a version, such as /kit/1.0.0/. The manifest lists every element, attribute, chart type and theme for agents.

API routes

  • POST /api/v1/reviews plan a new sheet
  • POST /api/v1/reviews/:id/versions plan its next version
  • GET /api/v1/reviews/:id inspect versions, files, and replies
  • PATCH /api/v1/reviews/:id change title, retention, note, category or workspace
  • GET /api/v1/workspaces list workspaces, the groupings of sheets
  • POST /api/v1/workspaces create a workspace
  • PATCH /api/v1/workspaces/:id rename a workspace
  • DELETE /api/v1/workspaces/:id delete a workspace; its sheets are unfiled
  • POST /api/v1/reviews/:id/share create or replace its shared link
  • DELETE /api/v1/reviews/:id/share turn the shared link off
  • POST /api/v1/reviews/:id/grants create a one-time read link for an agent
  • PUT /api/v1/reviews/:id/form set a reply form
  • GET /api/v1/reviews/:id/export?format=md export replies

Files: HTML, text, Markdown, CSV, JSON, PDF, images (PNG, JPEG, WebP, GIF, AVIF, SVG), video (MP4, WebM), audio (MP3, Ogg, WAV), ZIP and 3D models (GLB, glTF), up to 90 MiB each. Uploaded HTML runs without scripts unless you mark it interactive.

Get integration help