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.