VerSketch

VerSketch Developers

Agent API v0

A REST surface at /api/agent/v0 that lets an external agent read a plan, validate work through the same gates the editor uses, and propose changes. The write policy is proposal_only: an agent may propose, never commit. Every proposal waits as a ghost overlay until a human accepts it on the canvas, so nothing on this surface can overwrite or delete a user's work. Control requests and responses use JSON; the source-file chunk endpoint accepts raw binary bytes with an exact declared length and hash.

External model files. Source-file upload and model placement proposals are currently unavailable through the Agent API. Built-in catalogue resources and native proposal tools remain available.
Get a token. A signed-in user can now issue a one-time token pair for their local agent at Connect an agent. The token grants read, validate, and propose access; every proposal still needs a human to accept it in VerSketch. Each connection is its own AIVerID grant (hub Phase B1): revoking one agent cuts only that agent — other agents and the web session stay signed in. The hub's Connected Agents dashboard screen is on its way. The BYO prompt pack remains the zero-auth on-ramp.
One pair, one agent instance. Do not copy a token pair to two machines. Their refreshes can collide outside AIVerID's 30-second grace window and the hub cuts off the token family of that agent — other agents and the web session are unaffected. Connect the second machine again at Connect an agent to issue its own pair.
Refreshing. Access tokens live one hour. Renew by POSTing exactly {"grant_type":"refresh_token","refresh_token":"..."} (no other keys) to /api/agent/token/refresh; the response is AIVerID's token JSON passed through verbatim, and each refresh rotates the pair — always store the new one. Raw REST clients should follow that flow directly. MCP users can set both VERSKETCH_TOKEN and VERSKETCH_REFRESH_TOKEN; theversketch-mcp shim refreshes automatically and stores the rotated pair in ~/.versketch/agent-token.json (or VERSKETCH_TOKEN_STORE), using a local lock to avoid a same-machine rotation collision. A 400 invalid_grant carries "hint":"reconnect": the family is revoked, so send the user back to Connect an agent instead of retrying.

Connect from Claude / ChatGPT

Add a remote MCP server at https://versketch.xyz/api/agent/mcp. The connection uses your AIVerID account and always keeps VerSketch's proposal-only write policy: an agent may propose, never commit.

  • Claude: client ID aiv_claude; leave the client secret blank.
  • ChatGPT: client ID aiv_chatgpt; leave the client secret blank.

If the host asks for scopes, request profile email versketch:read versketch:validate versketch:propose. Model upload is optional and must be selected by the user. The MCP server advertises mutation tools only while the full upload path is operational; an existing transfer's get_model_source status remains available during an operational outage. The server enforces each listed tool's required scopes when it is called.

MCP validates each tool's envelope first. Envelope-shape errors use JSON-RPC -32602; once an input reaches a VerSketch core, its success or error body is the same REST body verbatim.

Authentication

Send an AIVerID access token on every request except the public discovery calls GET /capabilities, GET /schema, and GET /resources:

Authorization: Bearer <AIVerID access token>

VerSketch is introspection-based: each token is verified against the AIVerID hub per request, and VerSketch keeps no token store of its own. Authorization uses exact scope membership — the token's space-delimited scope string must contain the scope an endpoint requires.

  • versketch:read — read projects, working sets, BOQ, code-check reports and exports.
  • versketch:validate — stateless spec/patch validation.
  • versketch:propose — create proposals, and create new projects whose first content is a proposal.
  • Missing or malformed Authorization header → 401 invalid_token with WWW-Authenticate: Bearer (no error attribute, per RFC 6750 §3).
  • A presented token that is missing, revoked, expired, or not issued to VerSketch → 401 invalid_token with WWW-Authenticate: Bearer error="invalid_token".
  • A valid token without the required scope → 403 insufficient_scope with WWW-Authenticate: Bearer error="insufficient_scope", scope="…".

Agent requests never consume one of the account's device seats — the device ceiling applies to browser sessions only.

Conventions

  • One error shape across the whole surface: { "error": string, "message": string }, where error is the machine code. The 402 export_not_included refusal additionally carries tier, the plan the token subject is on.
  • Ownership is private: a project the token subject does not own answers 404 not_found, never 403 — the API does not reveal that someone else's project exists.
  • The native decoder caps request bodies at 8 MiB (413). Direct Vercel Function ingress rejects bodies over its 4.5 MB platform limit before route validation; keep encoded JSON requests within 4 MiB for this transport. The 8 MiB application ceiling requires an ingress that can carry it. JSON is capped at 64 nesting levels and 200,000 nodes, polygons at 1,000 points each and 20,000 points per spec (400 spec_too_deep / spec_too_large / spec_too_complex).
  • Read GET /resources before authoring a furnishing or material. It lists the actual built-in IDs and dimensions. The Agent API does not currently accept external 3D/CAD/BIM source files or import them into an existing project.
  • Rate limiting is fail-closed: if the limiter itself is unavailable the request is refused with 429 rather than waved through. Every 429 carries a Retry-After header (seconds).

Rate limits

  • 240/minute per IP on every authenticated endpoint, consumed before token introspection.
  • 120/minute per token subject across the whole surface.
  • 30/minute per subject for POST /validate.
  • 10/minute per subject for the versketch:propose endpoints.
  • 4/minute per subject for server-side IFC export; concurrency is capped at 1 active job per subject and 4 globally.

Endpoints

The table below renders from the same declaration GET /api/agent/v0/capabilities serves, so it cannot drift from what the API declares about itself.

POST/api/agent/v0/projects/:id/edit-previewversketch:validate

Prepare a deterministic move/resize-opening or translate/quarter-turn-group edit. Returns a validated patch and baseRevision; never writes a proposal or document. Architectural transforms require explicit dependency inclusion; room resize is unsupported. Discover operation arguments with get_authoring_schema.

  • versketch:validate scope; shared 30/min validation bucket; 64KB request limit.
  • {baseRevision, operation, ...arguments}. Operations: move_opening (openingId, offset mm); resize_opening (openingId, width mm, height? mm); translate_group (ids, dx mm, dy mm); rotate_group (ids, pivot:[x,y], angleDeg: ±90/±180/±270).
  • Building transforms require complete dependencies across all levels; includeLevelDependencies:true explicitly includes them. Read returned dependencyIds before proposing. Constraint-aware room resize is unsupported.
  • Grid axes/offsets rotate with the group. Shed highSide follows every quarter turn, including 180 degrees. Hip roofs require axis-aligned rectangles with long-axis ridges; quarter turns swap the ridge axis.
200 — {kind:"edit-preview", projectId, baseRevision, patch, affectedIds, dependencyIds, validation, nextStep}. No proposal or document is written. Submit the returned patch and baseRevision through the proposal endpoint only if the preview matches the request.
  • 400 bad_body/bad_selection/unknown_opening/unsupported_operation/dependent_selection_required/edit_invalid/no_change/patch_too_large.
  • 404 not_found; 409 revision_conflict; 413 payload_too_large; 503 db_error.

GET/api/agent/v0/projects/:id/preview?baseRevision=&level=&proposalId=versketch:read

Render a bounded 2D PNG of a saved project or proposal at an exact baseRevision. Returns an MCP image plus skipped/withheld-element report. Check the report: the image omits unsupported geometry and is not a 3D view.

  • versketch:read scope; separate 12/min rendering bucket.
  • Required baseRevision=current saved revision. Optional level=level ID and proposalId=owned proposal ID.
200 — {kind:"plan-2d", source, projectId, revision, proposalId, proposalStatus, levelId, mimeType:"image/png", imageBase64, width, height, report:{skipped,withheld,limitations}}. MCP returns the PNG as an image block and metadata as text. Read the report; unsupported geometry is explicitly withheld. Preview is bounded to 1024×1024 and is not a 3D rendering.
  • 400 bad_revision/bad_level/bad_preview_selector; 404 not_found; 409 stale_revision/stale_proposal; 413 preview_too_complex; 422 preview_invalid; 503 preview_unavailable/db_error.

GET/api/agent/v0/schema?detail=full|compact&elements=walls,slabsno auth

Discover canonical PlanSpec and PlanPatch authoring schemas, a valid create/edit example, units, limits, field preservation policy, and revision-safe human-review workflow. Read before authoring. detail=compact (the MCP default) replaces the JSON schemas with a per-element field summary at about a sixth of the size; elements= adds the exact schema of the named collections.

  • No authentication. Read before authoring. The response is not cached because optional model-upload readiness can be withdrawn. MCP: get_authoring_schema.
  • Optional ?detail=full|compact (REST default full; the MCP tool defaults to compact, about a sixth of the size: prose, limits, examples, patch grammar and a per-element field summary instead of the JSON schemas). Optional ?elements=walls,slabs adds elementSchemas with the exact PlanSpec item schema and PlanPatch upsert schema for those collections.
200 — { version, detail, profile, scope, instructions, schemas: { PlanSpec, PlanPatch }, portableSchemas, conventions, limits, examples, deterministicEdits, supported, unavailable, elements: { available, requiredOnCreate, summary, expand }, patchGrammar, elementSchemas? }. detail=compact omits schemas/portableSchemas and per-tool inputSchemas (available in tools/list), and adds tokenBudget.
The native-direct-agent profile is generated from the AI-authorable subset of the authoritative core TypeScript model; its direct-agent patch schema includes explicit supported null clear signals. Externally admitted importedAssets/importedModels are withheld because agents cannot inspect or fabricate their blob identity, and existing values survive proposal application. The profile has no managed-vendor optional-field ceiling. portableSchemas retain the narrower managed AI grammar. Both profiles still require document validation and do not establish building-code approval.
When artifactImport.upload is available, it contains the currently attested source formats, exact model tool schemas, required scopes and the source-first workflow. During an operational outage, artifactImport.sourceStatus may remain available so an agent can read an already-created transfer without starting or completing new work.
Read the full project and focused working set at the same revision; validate {spec, patch}; submit {patch, baseRevision}; hand the absolute reviewUrl to the user verbatim (reviewPath is the same link relative to the VerSketch origin). A 409 revision_conflict requires re-reading and rebuilding. Creation returns {projectId, proposalId, status:"pending", projectPath, reviewPath, projectUrl, reviewUrl}; edit proposals return the same links. Humans review and accept in Studio. Confirm accepted and resolved_revision before claiming the design is saved.

GET/api/agent/v0/resources?kind=all|furnishings|materials&category=&query=&limit=&fields=full|compactno auth

Discover actual built-in furnishing assetKeys and material IDs with dimensions, applicability, evidenced source/license provenance, and the GLB-display versus native-box downstream representation. Arbitrary 3D file upload/import is unavailable. category (furnishing category or palette group), query (substring) and limit narrow the answer; fields=compact (the MCP default) omits per-item provenance and the source registry. counts describe the answer, totals the whole catalogue.

  • No authentication. Optional ?kind=all|furnishings|materials (default all). The response is not cached because artifactFiles follows current operational readiness. MCP: list_resources.
  • Optional ?category= (a furnishing category or palette group from the categories block), ?query= (case-insensitive substring, max 64 chars), ?limit=1..200 and ?fields=full|compact (REST default full; the MCP tool defaults to compact, which omits per-item sourceAsset/sourceIds/sizeKB, material nameKey/textureKey/textureSourceId and the sources registry). Unknown values answer 400 bad_resource_category/bad_resource_query/bad_resource_limit/bad_resource_fields; the category refusal lists the available names.
200 — { version, kind, units:"mm", placement, modelRepresentation, artifactFiles, sources,
  furnishings:[{assetKey, kind:"model", sourceAsset, sourceIds, sizeKB,
    dimensionsMm:{width,depth,height}, category?, paletteGroup?, defaultMounting, sanitaryType?}],
  materials:[{materialId, nameKey, appliesTo, displayColor, textureKey?, textureSourceId?, floorFinish?, wallFinish?}],
  counts, totals, filter:{category, query, limit, fields, truncated}, categories:{category:{name:count}, paletteGroup:{name:count}} }
counts describe this answer; totals always describe the whole catalogue, so a filtered page is never mistaken for everything.
The response is derived from the built-in model catalogue and core material presets. Use only listed IDs. Catalogue GLBs are display geometry; the native PlanSpec, code check, BOQ and IFC export represent each one as its dimensioned furnishing box. A catalogue GLB does not infer walls, rooms, structure, services or whole-building semantics. artifactFiles reports the current model-upload state and attested formats; unavailable means no source-file ingress is being advertised.
  • 400 bad_resource_kind — kind is not all, furnishings, or materials.
  • 400 bad_resource_category / bad_resource_query / bad_resource_limit / bad_resource_fields — see request.

GET/api/agent/v0/capabilitiesno auth

This document: endpoints, required scopes, export formats.

  • No authentication. Cache-Control: no-store; optional model-upload endpoints and formats follow current operational readiness.
200 — the machine-readable version of this page:
{
  "version": "v0",
  "base": "/api/agent/v0",
  "auth": { "type": "oauth2_introspection", "issuer": "AIVerID", ... },
  "writePolicy": "proposal_only",
  "resources": "/api/agent/v0/resources",
  "endpoints": [ { "method", "path", "scope", "description", "annotations" }, ... ],
  "exportFormats": ["versketch.json", "dxf", "ifc", "boq.csv"],
  "unavailableExportFormats": ["gltf", "step", "dae", "usdz", "pdf"],
  "unavailableFormats": ["gltf", "step", "dae", "usdz", "pdf"], // deprecated export-only alias
  "artifactImport": { "upload": "available" | "unavailable", "importIntoExistingProject": "proposal_only" | "unavailable", "supportedUploadFormats": [...], "limits"?: {...} },
  "exportAccess": { "freeFormats": ["versketch.json"], "paidFormats": [...], ... },
  "errorShape": "{ \"error\": string, \"message\": string }",
  "rateLimits": {
    "perSubject": "120/minute",
    "validate": "30/minute",
    "propose": "10/minute",
    "ifcExport": "4/minute",
    "ifcConcurrentPerSubject": 1,
    "ifcConcurrentGlobal": 4,
    "ifcMaxExecutionSeconds": 240, ...
  }
}

GET/api/agent/v0/meversketch:read

The VerSketch account this connection acts as: AIVerID, connected host, granted scopes, plan and export access, project count and the three most recently updated project names.

  • versketch:read scope; no parameters. MCP: whoami. Answers which account a connection is bound to — useful after reconnecting a connector.
200
{ "aiverid": string, "connectedVia": string | null, "scopes": string[],
  "plan": { "tier": string, "exports": { "versketch.json": boolean, "dxf": boolean, "ifc": boolean, "boq.csv": boolean }, "boq": boolean },
  "projects": { "count": number, "recent": [ { "id": string, "name": string, "updated_at": string } ] },
  "hint": string }
aiverid is the AIVerID subject; connectedVia is the acting host the AIVerID hub named for this grant. No email or display name is returned: VerSketch receives neither. plan.exports and plan.boq are computed by the same gate the export and BOQ endpoints enforce, for live projects (a Paid Archive snapshot may still export what it froze). projects.count counts live, non-archived projects; recent lists at most three, most recently updated first.
  • 503 db_error — project store unavailable; 503 entitlement_unavailable — plan could not be read; retry shortly.

GET/api/agent/v0/projectsversketch:read

List projects owned by the token subject.

  • Lists projects owned by the token subject in immutable creation order. Each summary includes its current pendingProposals count. Optional limit is an integer from 1 to 100 (default 50). Optional cursor is the opaque nextCursor from the preceding page; unknown, duplicate, or malformed query parameters are refused. Restart from the first page to include projects created during an earlier traversal.
200
{ "projects": [ { "id": string, "name": string, "created_at": string, "updated_at": string, "pendingProposals": number } ], "nextCursor": string | null }
  • 400 invalid_pagination — limit or cursor is invalid, duplicated, or accompanied by an unknown parameter.
  • 503 db_error — project store unavailable; retry shortly.

GET/api/agent/v0/projects/:idversketch:read

Full PlanSpec + revision of one owned project.

  • Returns the full PlanSpec and the current document revision.
200
{ "project": { "id", "name", "created_at", "updated_at", "pendingProposals": number }, "spec": PlanSpec, "revision": number }
  • 404 not_found — no such project, or the token subject does not own it (ownership is never revealed as 403).
  • 503 db_error — store unavailable.

GET/api/agent/v0/projects/:id/working-set?query=versketch:read

Bounded working set in the exact shape the patch prompt consumes (64KB cap).

  • Optional ?query= (at most 4,000 characters) focuses the working set on the elements relevant to that request.
  • Returns the bounded context the AI patch prompt consumes (64 KB cap), so an agent sees exactly what a model would see.
200
{
  "mode": "full" | "compacted",
  "text": string,
  "editableIds": string[],
  "includedElements": number,
  "totalElements": number,
  "contextJsonLength": number,
  "caps": { "full": number, "compact": number },
  "revision": number
}
  • 400 query_too_long — the query exceeds 4,000 characters.
  • 404 not_found — unknown or unowned project.
  • 503 db_error — store unavailable or the stored spec could not be read.

POST/api/agent/v0/validateversketch:validate

Stateless document validation: {spec} or {spec, patch} → draft/deliverable/completeness verdicts; with a patch, completeness blocks only errors the patch adds (base errors listed as preExisting). This is not building-code approval; code-check needs body.region or spec.meta.regionHint and is skipped if a requested patch fails.

  • Body: { "spec": PlanSpec } or { "spec": PlanSpec, "patch": PlanPatch }, optional "region" (US, CA, UK, EU, AU, TH) — 8 MiB application ceiling (the hosting ingress can be lower).
  • Stateless: nothing is stored; the verdict depends only on the body.
  • A broken spec is a 200 with a failing verdict — "your spec is invalid" is the answer, never a 500. Only a malformed envelope is a 400.
  • The code-check summary runs only when the spec is draft-valid and a jurisdiction is known (explicit "region" or a persisted spec.meta.regionHint). VerSketch never guesses a jurisdiction.
200
{
  "verdict": "pass" | "fail",
  "gates": {
    "draft": { "ok": boolean, "errors": string[] },
    "deliverable": { "ok", "errors" } | null,   // null when an earlier gate failed
    "completeness": { "ok", "errors" } | null
  },
  "patch": { "requested": boolean, "applied": boolean, "error": string | null },
  "codeCheck": {
    "region", "regionSupported", "codeAuthority",
    "summary": { "errors", "warnings", "info", "total" },
    "levels": [ { "levelId", "skippedReason": "pool" | null, "skipIneligibilityReason": "pool_has_spaces" | "pool_has_openings" | null, "errors", "warnings", "info", "total" } ],
    "disclaimer": string
  } | null,
  "codeCheckSkippedReason": "region_required" | "draft_invalid" | "patch_invalid" | null,
  "validatedSpec": PlanSpec | null,             // the merged spec when a patch applied
  "advisories": [{ "code": string, "message": string }]
}
  • 400 bad_body / bad_json — the envelope is not { spec } or { spec, patch }.
  • 400 bad_region — region is not one of US, CA, UK, EU, AU, TH.
  • 400 spec_too_deep / spec_too_large / spec_too_complex — JSON or polygon budget exceeded.
  • 413 payload_too_large — body exceeds the 8 MiB application ceiling (the hosting ingress can be lower).

GET/api/agent/v0/projects/:id/boq?format=json|csvversketch:read

Bill of quantities derived from the PlanSpec. Requires a plan with export (402 export_not_included otherwise).

  • Optional ?format=json|csv (default json). The bill of quantities is derived from the stored PlanSpec — the same numbers the in-app BOQ panel shows.
  • Requires a plan with export: the quantities are the export, so this endpoint enforces the same entitlement as /export.
200 (json)   { "items": VbmBoqItem[], "revision": number }
200 (csv)    { "csv": string, "revision": number }
  • 400 bad_format — format is not "json" or "csv".
  • 402 export_not_included — the subject plan has no export; the body carries "tier".
  • 404 not_found — unknown or unowned project.
  • 422 export_invalid — a specific element cannot be quantified; the message names it.
  • 503 entitlement_unavailable — the plan could not be confirmed (Retry-After: 30). 503 db_error — store unavailable.

GET/api/agent/v0/projects/:id/code-check?region=versketch:read

Informational code-check report per level (with the legal disclaimer). Requires ?region= or a persisted spec.meta.regionHint; VerSketch does not guess a jurisdiction.

  • Jurisdiction is required: pass ?region=US|CA|UK|EU|AU|TH or persist spec.meta.regionHint. Neither present is a 400 — building rules are never guessed.
  • Informational report per level, with the mandatory legal disclaimer. A pool is returned with skippedReason: "pool" only when it has neither rooms nor door/window openings; otherwise skipIneligibilityReason names why building-room checks still run. AI flags, a licensed professional verifies, the authority having jurisdiction approves.
200
{
  "region", "regionSupported", "codeAuthority", "supportedRegions",
  "levels": [ {
    "levelId", "skippedReason": "pool" | null, "skipIneligibilityReason": "pool_has_spaces" | "pool_has_openings" | null, "violations": CodeViolation[],
    "summary": { "errors", "warnings", "info", "total" },
    "roomCoverage": "none" | "partial" | "complete",
    "roomDetectionTruncated": boolean
  } ],
  "summary": { "errors", "warnings", "info", "total" },
  "disclaimer": string,
  "revision": number
}
  • 400 region_required — no ?region= and no persisted spec.meta.regionHint.
  • 400 bad_region — unsupported region value.
  • 404 not_found — unknown or unowned project.
  • 503 db_error — store unavailable or the engine failed.

GET/api/agent/v0/projects/:id/export?format=versketch.json|dxf|ifc|boq.csvversketch:read

Server-side export. versketch.json is the free user-owned project file; dxf, ifc, and boq.csv require a plan with export (402 export_not_included otherwise). Mesh-kernel formats (gltf/step/dae/usdz/pdf) answer 501.

  • Optional ?format=versketch.json|dxf|ifc|boq.csv (default versketch.json). DXF also accepts ?level= (defaults to the first level).
  • versketch.json is the free, user-owned project file on every plan. dxf, ifc and boq.csv require a plan with export.
  • Mesh-kernel formats (gltf, step, dae, usdz, pdf) need the browser kernel and answer 501 in v0 — export those from the VerSketch UI.
200 (versketch.json)  { "format": "versketch.json", "spec": PlanSpec, "revision": number }
200 (dxf | boq.csv)   { "format", "content": string, "contentType", "skipped": SkippedElement[], "revision" }
200 (ifc)             { "format": "ifc", "content": base64 string, "contentType": "application/x-step", "skipped", "revision" }
  • 400 bad_format — unknown format. 400 no_levels / bad_level — DXF level selection failed.
  • 402 export_not_included — paid format on a plan without export; the body carries "tier".
  • 404 not_found — unknown or unowned project.
  • 422 export_invalid — a specific element cannot be exported; the message names it.
  • 501 format_not_available_v0 — mesh-kernel format; use the UI export.
  • 503 entitlement_unavailable (Retry-After: 30) / db_error.

POST/api/agent/v0/projects/:id/proposalsversketch:propose

Submit {patch, baseRevision, summary?} against an owned project. Validated through the same gates as the editor, then held as a pending proposal for a human to accept on the canvas. Never modifies the plan.

  • Body: { "patch": PlanPatch, "baseRevision": integer, "summary"?: string } — 8 MiB application ceiling (the hosting ingress can be lower), summary at most 2,000 characters.
  • "baseRevision" must equal the current server revision (read it from GET /projects/:id); a mismatch is a 409 and the agent re-reads and re-proposes.
  • The patch is applied with the same grammar the in-app AI rail uses, and the merged spec must pass every validation gate before the proposal is stored.
  • A proposal never touches the document. It waits as a ghost overlay until a human accepts or rejects it on the canvas. At most 10 pending proposals per project.
201
{ "proposalId": string, "status": "pending", "baseRevision": number }
  • 400 bad_json / bad_body — unparseable body, missing patch, or a non-integer baseRevision.
  • 400 proposal_invalid — the patch failed to apply, or the merged spec failed a gate; the message carries the per-gate errors.
  • 400 spec_too_deep / spec_too_large / spec_too_complex — JSON or polygon budget exceeded.
  • 409 revision_conflict — the document moved past baseRevision.
  • 409 too_many_pending — the project already holds 10 pending proposals.
  • 413 body_too_large — body exceeds the 8 MiB application ceiling (the hosting ingress can be lower).
  • 404 not_found / 503 db_error.

GET/api/agent/v0/projects/:id/proposalsversketch:read

List this subject's proposals on a project with their status.

  • Lists this subject’s proposals on the project — metadata and status, without the spec payloads.
200
{ "proposals": [ {
  "id", "project_id", "aiverid", "base_revision", "summary",
  "status": "pending" | "accepted" | "rejected" | "superseded" | "expired",
  "created_at", "resolved_at", "resolved_revision"
} ] }
  • 404 not_found — unknown or unowned project.
  • 503 db_error — store unavailable.

GET/api/agent/v0/projects/:id/proposals/:pidversketch:read

One proposal: status, base and proposed specs, resolved revision once accepted.

  • One proposal with its base and proposed specs — this is how an agent learns whether the human accepted its work and at which revision it landed (resolved_revision).
  • "expired" means the pending proposal aged out before anyone resolved it; nothing was merged.
200
{ ...proposal row as above, "base_spec": PlanSpec, "proposal_spec": PlanSpec }
  • 404 not_found — unknown project or proposal.
  • 503 db_error — store unavailable.

POST/api/agent/v0/projectsversketch:propose

Create an empty project and attach {name, spec, summary?, operationId?, locale?} to it as its first pending proposal. The plan itself lands only when a human accepts. locale (BCP 47) names the empty project's starter level in the user's language.

  • Body: { "name": string, "spec": PlanSpec, "summary"?: string, "operationId"?: string, "locale"?: string } — 8 MiB application ceiling (the hosting ingress can be lower).
  • "name" is required, at most 200 characters. "summary" is optional, at most 2,000 characters.
  • "locale" is optional: a BCP 47 language tag for the person you are working with (for example "th" or "ja-JP"). The empty project's starter level is named in that language while the proposal waits for review; a language without a VerSketch dictionary uses English, and a malformed tag is refused with bad_body.
  • The spec runs the full gate chain (draft structure, deliverable, completeness) before anything is stored.
  • Creates an empty project and attaches the spec as its first pending proposal. The plan itself lands only when a human accepts it on the canvas.
  • If a create response is absent or times out, list projects and inspect the newest matching project before retrying. Reuse the same operationId only for the same intent.
201
{ "projectId": string, "proposalId": string }
  • 400 bad_json / bad_body — unparseable body, missing name or spec, or an invalid summary.
  • 400 spec_too_deep / spec_too_large / spec_too_complex — JSON or polygon budget exceeded.
  • 400 proposal_invalid — the spec failed to parse or failed a validation gate; the message carries the per-gate errors.
  • 409 project_limit — the plan tier project ceiling is reached.
  • 413 body_too_large — body exceeds the 8 MiB application ceiling (the hosting ingress can be lower).
  • 503 db_error — store unavailable.

Worked examples

One request per scope tier, with placeholder tokens. Shapes match the responses documented above.

# No token needed — discover the surface
curl https://versketch.xyz/api/agent/v0/capabilities
# versketch:read — list the token subject's projects
curl -H "Authorization: Bearer $AIVERID_TOKEN" \
  "https://versketch.xyz/api/agent/v0/projects?limit=50"

# → 200
# { "projects": [ { "id": "8b1f…", "name": "Pool villa",
#     "created_at": "2026-08-01T09:30:00Z", "updated_at": "2026-08-14T17:02:11Z" } ],
#   "nextCursor": "eyJjcmVhdGVkQXQi…" }

# Continue with the opaque cursor exactly as returned:
curl -H "Authorization: Bearer $AIVERID_TOKEN" \
  "https://versketch.xyz/api/agent/v0/projects?limit=50&cursor=eyJjcmVhdGVkQXQi…"
# versketch:validate — gate a spec without storing anything
curl -X POST https://versketch.xyz/api/agent/v0/validate \
  -H "Authorization: Bearer $AIVERID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "spec": { /* your PlanSpec — the "spec" payload of a .versketch.json file */ }, "region": "US" }'

# → 200 (abridged)
# { "verdict": "pass",
#   "gates": { "draft": { "ok": true, "errors": [] },
#              "deliverable": { "ok": true, "errors": [] },
#              "completeness": { "ok": true, "errors": [] } },
#   "patch": { "requested": false, "applied": false, "error": null },
#   "codeCheck": { "region": "US", "summary": { "errors": 0, "warnings": 2, "info": 5, "total": 7 }, … },
#   "codeCheckSkippedReason": null,
#   "validatedSpec": null,
#   "advisories": [] }
# versketch:propose — submit a patch for a human to accept
curl -X POST https://versketch.xyz/api/agent/v0/projects/$PROJECT_ID/proposals \
  -H "Authorization: Bearer $AIVERID_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "patch": { /* plan patch */ }, "baseRevision": 12,
        "summary": "Widen the entry door to 900 mm" }'

# → 201
# { "proposalId": "6f0a…", "status": "pending", "baseRevision": 12 }
#
# The change now waits as a ghost on the owner's canvas. Poll
# GET /projects/$PROJECT_ID/proposals/6f0a… to see whether it was accepted.

Zero-auth today: the BYO prompt pack

You do not need a token to put an external model to work. Inside the app — in the project gallery's import dialog and in the editor — the "use your own AI" button copies a prompt pack: the same system prompt and plan briefs the managed pipeline sends. Paste it into any assistant, then paste the JSON it returns back into VerSketch. The pasted plan passes through exactly the same validation gates as everything else on this page, and appears as a proposal for you to accept. No account is required to try it.