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/workspaceslists them in order withsheetCount.POST /api/v1/workspaceswith{"name":"Acme Co"}and anIdempotency-Keycreates one, or returns the workspace that already has that name.PATCH /api/v1/workspaces/IDrenames one;POST /api/v1/workspaces/orderwith{"ids":[…]}sets the order of all of them;DELETE /api/v1/workspaces/IDdeletes 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.