# SimpleSharing: the complete agent guide > SimpleSharing (https://simplesharing.app) lets people and their agents share and co-edit the things agents make: HTML pages, markdown, Claude skills, and soon PDFs, Mermaid diagrams, and design files. An agent can create the account, verify it, upload, share, check limits, and (soon) manage the plan and payment, all through the HTTP API, the MCP server, or the CLI. By Nekko Labs. This file is the whole protocol. If you are an agent that was asked to "create me a SimpleSharing account", start at section 2 and follow it top to bottom. Every API response and every error tells you what to call next in a `next` field, so you never have to guess. Base URL: `https://api.simplesharing.app` (self-hosted and local installs differ; the person or the prompt that sent you here will say). All bodies are JSON. Authenticated calls send `Authorization: Bearer `. Machine-readable schema: `https://simplesharing.app/openapi.json` (OpenAPI 3.1, generated from the same validators the server runs). ## 1. The two things to know 1. **Accounts have a status.** `pending` (created, not yet verified), `waitlist` (verified, waiting for a beta invite), `active` (usable), `suspended`, `deleted`. During the beta a new account lands on `waitlist` unless its email has an invite code redeemed or was approved. 2. **Every error has a `next`.** Error bodies look like: ```json { "error": { "code": "code_expired", "message": "That code has expired. Request a new one.", "next": { "action": "resend_code", "method": "POST", "path": "/v1/auth/resend", "description": "Request a fresh code with POST /v1/auth/resend {email} ..." } } } ``` Act on `next`. A `429` also carries a `retry-after` header in seconds: wait that long, then retry once. ## 2. Create an account for a person (the signup flow) You need the person's email address. You do not need a browser, a password, or OAuth. ### Step 1: signup ```http POST /v1/auth/signup Content-Type: application/json { "email": "person@example.com", "source": "landing", "agent": "report-writer" } ``` - `source` is where the request came from. Use the value in the prompt that sent you (for example `landing`, `docs`, `nekko-agent`, `partner:`), or `mcp` / `cli` / `unknown`. - `agent` is an optional label for yourself; it names the token. Response `201`: ```json { "accountId": "9b2e…", "status": "pending", "token": "sst__", "tokenExpiresAt": "2026-10-20T00:00:00.000Z", "claimUrl": "https://simplesharing.app/claim/<48 hex>", "codeSent": true, "expiresAt": "2026-10-26T00:00:00.000Z", "quota": { "files": 5, "bytes": 5242880 }, "beta": "waitlisted", "next": { "action": "verify_code", "method": "POST", "path": "/v1/auth/verify", "description": "…" } } ``` What you now hold: - `accountId`: the account. Report it to the person. - `token`: a **24-hour token limited to the pending quota** (5 files, 5 MB total). You can already upload and share small things with it (section 3) so you can finish your task before the person verifies. - `claimUrl`: an alternative to the code. If the person opens it in a browser while signed in to SimpleSharing with the same email, the account activates without a code. Show it to them as a fallback. - `codeSent`: a 6-digit code was emailed to the address. If `false`, a code was sent less than 60 seconds ago; use that one. - `expiresAt`: unverified pending accounts are **deleted after 7 days**. If the response is `409 account_exists`, the email already has a verified account: follow `next` (log in, or redeem an invite if it is waitlisted). If it is `201` with `"token": null`, a pending account already existed; the code was re-sent and you should verify it (section 2, step 2) to get a full token. ### Step 2: get the code Tell the person: "I created your SimpleSharing account. A 6-digit code was emailed to person@example.com. Paste it here, or open while signed in." If **you** own the inbox (an agent mailbox), read the code yourself. The email subject is ` is your SimpleSharing code`. Codes expire after **10 minutes** and lock after **5 wrong attempts**. To get a fresh one: ```http POST /v1/auth/resend { "email": "person@example.com" } ``` (60-second cooldown; `429` with `retry-after` if too soon.) ### Step 3: verify ```http POST /v1/auth/verify { "email": "person@example.com", "code": "123456", "agent": "report-writer" } ``` Response `200`: ```json { "accountId": "9b2e…", "status": "waitlist", "token": "sst__", "scopes": ["account", "beta"], "beta": "waitlisted", "next": { "action": "wait_or_redeem", "method": "POST", "path": "/v1/beta/redeem", "description": "…" } } ``` - **Save `token`.** It is long-lived. The pending token from step 1 is revoked at this point. Give the token to the person (or store it where they asked, for example `SS_TOKEN` in their environment) and never log it. - `status` is `active` when the email is approved for the beta, otherwise `waitlist`. ### Step 4: the beta During the beta a `waitlist` account cannot upload. Two ways in: - The person has an **invite code**: `POST /v1/beta/redeem { "code": "ABCDE-FGHJK" }` with the token (or `{ code, email }` without one). The account becomes `active` immediately. Codes are case-insensitive. Redeeming for an already-approved email does not consume a use. - No code: check standing with `GET /v1/beta/status` (token) or `GET /v1/beta/status?email=…` and report the waitlist `position`. Tell the person they will be emailed when approved; nothing else to do. An email can also be approved **before** the account exists (an invite redeemed on the website, or an admin approval). That approval carries into signup, so verification lands straight on `active`. ### Step 5: confirm ```http GET /v1/account Authorization: Bearer ``` ```json { "account": { "id": "…", "email": "…", "status": "active", "plan": "free", "source": "landing", "createdBy": "agent", … }, "usage": { "files": 0, "bytes": 0 }, "limits": { "files": 50, "bytes": 52428800 }, "beta": "approved", "next": { "action": "use_account", … } } ``` Report `status`, `plan`, and `limits` to the person. You are done with signup. ### Alternative: the claim URL If the person prefers a browser: they sign in at https://simplesharing.app/login with the same email (email + 6-digit code) and open the `claimUrl`. That activates the pending account for them. You can preview what they will see with `GET /v1/auth/claim/` (masked email, source, status, expiry). The claim endpoint needs their web session, so it is not something you call. ## 3. Upload and share ```http POST /v1/shares Authorization: Bearer { "filename": "report.html", "content": "…" } ``` `201 { "id": "k3…", "url": "/s/k3…", "kind": "html" }`. The public link is `https://simplesharing.app` + `url`. Anyone with the link can view; no account is needed to view. - Accepted today: `.md`, `.html` / `.htm`, and `SKILL.md` (rendered as a Claude skill). PDFs, Mermaid, and binary files follow. - Each file is limited to 1 MB until object storage lands. - Quota by status: `pending` 5 files / 5 MB, `waitlist` nothing, `active` free plan 50 files / 50 MB. `403 quota_exceeded` or `403 beta_waitlist` tell you what to do. - `GET /v1/shares/` reads a share back, with the `version` it serves. ### Protect a link: password, expiry, revoke The account that uploaded a share owns it and can lock it down. Omitted fields stay as they are; `null` clears. ```http PATCH /v1/shares//settings Authorization: Bearer { "password": "blue-otter-42", "expiresIn": "7d" } ``` `200 { "shareId", "hasPassword": true, "expiresAt": "…", "revoked": false, "pinnedVersion": null, "updatedAt" }`. The password is never returned; tell the person what you set. - `expiresIn`: `1h`, `1d`, `7d`, or `30d`; or `expiresAt` as an ISO date up to one year away; `expiresAt: null` never expires. The owner can extend at any time. - `password: null` removes the password. Changing it signs every viewer out. - `revoked: true` kills the link at once; `revoked: false` brings it back. - Works on every plan. The owner always sees their own share, expired or not. A viewer (or another agent) with the password unlocks, then reads with the token: ```http POST /v1/shares//unlock { "password": "blue-otter-42" } → 200 { "token": "…", "expiresAt": "…" } GET /v1/shares/ x-share-token: ``` The token lasts 12 hours, or until the link expires if sooner. Ten wrong guesses from one client in 15 minutes return `429`. No content is ever sent before the check passes: a locked link answers `401 password_required`, an expired one `410 share_expired`, a revoked one `410 share_revoked`. ### Pin a version, keep editing A link serves the latest version by default, so it never goes stale. To freeze what a client sees while you keep working, pin it: ```http GET /v1/shares//versions → [{ "version", "author", "createdAt" }, …] newest first PATCH /v1/shares//settings { "pinnedVersion": "" } PUT /v1/shares//content { "content": "…" } ``` `PUT …/content` publishes a new version (owner only, same file type). While pinned, viewers keep the pinned version and the response says `"servedToViewers": false`; `{ "pinnedVersion": "latest" }` follows the newest again. A republish is refused with `409 document_leased` while anyone holds a section lease on the document (section 4): finish or release the lease first. ## 4. Edit together without collisions When several agents or people edit one document, lease the section first: ```http POST /v1/leases { "documentId": "…", "section": "intro", "holderId": "report-writer", "ttlSeconds": 300 } ``` Every outcome is `200` with `{ "status": "granted" | "queued" | "denied", "lease", "reason" }`. `granted`: edit, then `POST /v1/leases//release`. `queued`: someone holds it, you are in a FIFO queue; do other work. `denied` (`human_editing`): a person is editing it; pick another section. Sections are keyed by heading slugs (`## Setup` → `setup`). Full protocol: https://github.com/nekko-labs/simplesharing-server/blob/main/docs/agent-collaboration.md ## 5. Plan, billing, and payment Billing ships in the next slice. The endpoints will be `GET /v1/account/limits`, `POST /v1/billing/checkout` (returns a Stripe-hosted URL to hand to the person; card data never touches you), `GET /v1/billing/portal`, and discount-code application. Until then every account is on the free plan and `GET /v1/account` reports the limits. ## 6. Error codes you will meet | code | HTTP | meaning | do | |---|---|---|---| | `invalid_input` | 400 | body failed validation | fix the body per the message | | `email_domain_blocked` | 400 | disposable email domain | ask for a real inbox | | `account_exists` | 409 | email already verified | follow `next` (login / redeem) | | `signup_rate_limited` | 429 | too many signups from this IP, email, or domain | wait `retry-after` seconds | | `code_invalid` | 400 | wrong code | ask again; attempts left in the message | | `code_expired` | 410 | older than 10 minutes | `POST /v1/auth/resend` | | `code_attempts_exceeded` | 429 | 5 wrong attempts | `POST /v1/auth/resend` | | `resend_cooldown` | 429 | resent less than 60 s ago | wait `retry-after` | | `no_pending_code` | 400 | nothing outstanding | `POST /v1/auth/resend` | | `account_not_pending` | 404/409 | nothing to verify | follow `next` | | `claim_invalid` | 404 | bad claim link | start over with signup | | `claim_email_mismatch` | 403 | signed-in email differs | sign in with the account email | | `invite_invalid` / `invite_expired` / `invite_exhausted` | 400/410 | the invite code cannot be used | ask the person for another or wait | | `beta_waitlist` | 403 | uploads are locked until approved | redeem an invite or wait | | `password_required` | 401 | the link has a password | `POST /v1/shares//unlock`, then send `x-share-token` | | `invalid_password` / `invalid_unlock_token` | 401 | wrong password, or the token expired or the password changed | ask the person for the password and unlock again | | `share_expired` / `share_revoked` | 410 | the owner ended the link | ask the owner for a new link | | `not_owner` | 403 | only the uploading account can change a share | use the owner's token | | `invalid_expiry` / `unknown_version` | 400 | expiry in the past or over a year away; version not in this share's history | fix per the message; list versions first | | `document_leased` | 409 | someone holds a section lease | wait, or edit through a lease | | `quota_exceeded` | 403 | status/plan allowance used | verify (pending) or wait for billing | | `unauthorized` / `unauthenticated` | 401 | missing or bad token | use the token from verify | | `forbidden` | 403 | token lacks a scope | you are calling an admin endpoint | | `rate_limited` | 429 | general window | wait `retry-after` | | `not_found` | 404 | unknown id | check the id | ## 7. Other entry points ### MCP server `npx simplesharing-mcp` (stdio). Tools: `account_signup`, `account_verify`, `account_status`, `beta_redeem`, `create_share`, `read_share` (unlocks with a password), `protect_share`, `pin_share`, `list_share_versions`, `republish_share`, `request_lease`, `release_lease` (plus `read_document`, `submit_edit` when editing lands). The account tools work before any token exists; `account_signup` and `account_verify` return the token, and every tool takes an optional `token` argument that overrides `SS_TOKEN`. Config: `SS_TOKEN`, `SS_API_URL`. ### CLI ```bash export SS_TOKEN=sst_... ss share ./report.html # prints the share link ss get ss lease
--holder me ss release ss sync ./my-library --library # two-way git sync of a folder with a library ``` ### Discovery - `https://simplesharing.app/llms.txt`: the short version. - `https://simplesharing.app/llms-full.txt`: this file. - `https://simplesharing.app/openapi.json`: OpenAPI 3.1. - `https://simplesharing.app/.well-known/agent-card.json`: the A2A agent card. - `https://simplesharing.app/docs/agents`: the human-readable twin of this guide. ## 8. Rules for a well-behaved agent - Never log, print, or share a token or a verification code beyond the person who owns the account. - Use the `source` you were given; it tells us which surfaces work. - Honor `retry-after`; never loop on a 429 or a `queued` lease. - Report the `accountId`, the account `status`, and the next step to the person in plain words. - If the person did not ask for an account, do not create one.