Plansmith API

A versioned JSON API at api.plansmith.co/v1, authenticated with a bearer credential.

Document collaboration

Document collaboration routes under /v1/documents expose threads, replies, suggestions and eligible mention candidates. /v1/notifications exposes the caller's Inbox, read state and email preferences. Mutations keep the existing workspace, credential and document access checks. Use stable request IDs for retries and the returned revision for a review decision. See Comments and suggested edits.

Start here, with no credential

GET /v1 answers without one. It returns the link map, how to get a credential, and where the OpenAPI document lives, so a client can learn the shape of the API before anyone has minted anything.

curl https://api.plansmith.co/v1

Send a credential on the same call and you get your workspace, your scope and the credential kind back instead. A credential that is present and invalid is still refused with a 401: only the absence of one is anonymous.

Authentication

curl -H "Authorization: Bearer psk_write_…" https://api.plansmith.co/v1/me

Two credential kinds work on the same header: a personal API key (psk_…) minted in Settings → API keys, and an OAuth access token obtained by the CLI or an MCP client. Both act as the human they belong to.

Scopes

A credential is read or write. Scope is a ceiling on top of your own permissions, never a widening of them: a write key held by someone with view-only access to a plan still cannot change that plan.

Errors

{
  "error": {
    "code": "not_found",
    "message": "No such plan."
  }
}

A plan you cannot read answers 404, exactly like a plan that does not exist. That is deliberate: a 403 would confirm which private plans are real, so the two are made indistinguishable. The one 403 you will see on plan content is when you can read a plan but not edit it.

How things are addressed

Projects have a slug, plans are PLAN-n within their project, and specs are SPEC-n within their plan: the same handles you see in the web app, so a URL you read in the console is a path you can call.

/v1/projects/recipe-box
/v1/projects/recipe-box/plans/PLAN-12
/v1/projects/recipe-box/plans/PLAN-12/specs/SPEC-3
/v1/prototype-template                                # the workspace's, and its project twin
/v1/projects/recipe-box/prototype-template

Images

POST /v1/projects/recipe-box/plans/PLAN-12/images              # multipart
POST /v1/projects/recipe-box/plans/PLAN-12/images/upload-url   # for files over 4 MB

Send exactly one of a file part, a url (public https, fetched by the server), a storage_ref from upload-url, or an attachmentId already on the plan. Add spec=SPEC-3 or epic=true to append the image to that document, and alt for its text. The response carries attachmentId, url, the markdown to paste anywhere, appendedTo and the document's new revision.

Requests over about 4.5 MB never reach the API, so a larger file (up to 10 MB) goes in two steps: upload-url returns a signed URL and a storage_ref; PUT the bytes to the URL, then post the storage_ref with filename and content_type within 15 minutes.

An append is compare-and-set. Pass expectedRevision (the revision from your last read of the spec or the epic) to make sure you add to the version you read. A 409 means the document changed and nothing was added; its details carry attachmentId and currentRevision, so the retry reuses the stored image rather than uploading it again. PATCH …/specs/SPEC-3 and PUT …/epic take the same expectedRevision. SVG and other formats are a 422, a file over 10 MB a 413.

Prototypes

A plan's prototype answers metadata by default (status, size, whether the specs changed since it was built) and the HTML document with html=1. Add standalone=1 to that for a page that opens safely on its own in a browser: the prototype runs inside the same sandbox the web app uses.

GET /v1/projects/recipe-box/plans/PLAN-12/prototype                       # metadata
GET /v1/projects/recipe-box/plans/PLAN-12/prototype?html=1                # the document
GET /v1/projects/recipe-box/plans/PLAN-12/prototype?html=1&standalone=1   # a page to open

A document over the size limit answers 413, and the metadata says so first with tooLarge. The standalone page adds a little around the document, so one just under the limit can be refused there and still download plain. standalone=1 without html=1 answers 400.

Sharing

Sharing lives on the top-level plan and covers every sub-plan under it, so a call addressed at a sub-plan changes the plan above it.

GET    /v1/projects/recipe-box/plans/PLAN-12/sharing
PUT    /v1/projects/recipe-box/plans/PLAN-12/sharing/visibility
POST   /v1/projects/recipe-box/plans/PLAN-12/sharing/collaborators
PATCH  /v1/projects/recipe-box/plans/PLAN-12/sharing/collaborators/{userId}
DELETE /v1/projects/recipe-box/plans/PLAN-12/sharing/collaborators/{userId}

All four mutations land on the top-level plan, PATCH included: promoting someone from viewer to editor grants them edit access across every sub-plan in the tree.

The GET carries isSubPlan, the root's name and PLAN-n, and subPlanCount: the ACTIVE descendants, or null when the count could not be read. Read isSubPlan rather than comparing rootPlanId to the handle you called with. One is a UUID and the other is a PLAN-n, so that comparison reports every plan as a sub-plan.

Every successful mutation returns scopeNote, one sentence naming the plan the change landed on. It is optional, not nullable: it is absent only when the read that composes it failed, which never turns a committed change into an error.

publicLinkCount is a number or the string "unknown". A failed read reports "unknown", never 0, because 0 would claim an exclusivity it cannot prove.

The full reference

Every route, parameter and response shape is described by the OpenAPI document. It needs no credential, and it is served from two addresses: the API host, and the site root, which is where a client that only knows the domain will look.

curl https://api.plansmith.co/v1/openapi.json
curl https://www.plansmith.co/openapi.json

https://www.plansmith.co/.well-known/api-catalog lists all of it in RFC 9727 form: the OpenAPI document, these guides, and the MCP endpoint.