Site Plan & CAD Handoff — Developer Docs
Read Site Plan Studio design data, compile a concept from the interview, and export a concept picture or geometry for Civil 3D.
How do I read site-plan design data and export concept geometry?
All four endpoints require the siteplan:read scope. Every key and connector token carries the full scope set by default, so no separate grant step exists.
| Endpoint | What it does | Rate limit |
|---|---|---|
GET /v1/projects/{project_id}/site-plan | Latest compiled concept: object_counts (every object counted by type), metrics and scenario, plus WGS84-only GeoJSON features by default; include_geometry=false returns an empty FeatureCollection | 60/min |
GET /v1/projects/{project_id}/design-criteria | Resolved zoning standards per district with per-field provenance | 60/min |
GET /v1/projects/{project_id}/yield-scenario | Settled yield scenarios per district plus snapshot summaries | 60/min |
POST /v1/projects/{project_id}/site-plan-exports | Renders the concept to a picture (concept_png), LandXML, DXF, or GeoJSON and returns a one-time download link | 10/min |
Concept geometry: until a concept compiles in Site Plan Studio, the site-plan route returns has_concept: false with an empty feature collection. Features carry object_type, label, layer, status, editable, and measurements; projected coordinates are stripped, WGS84 only. measurements holds site numbers only - solver bookkeeping (planner strategy, fit ratios, discarded-geometry counters) is excluded, so nothing there should be read as an area or dimension on the ground.
For a summary, pass include_geometry=false: has_concept, revision, object_counts, metrics and scenario still describe the plan, while feature_count is 0 and truncated is false. An empty FeatureCollection in that response does not mean the concept is absent. object_counts counts every plan object, including those beyond the geometry response cap, in descending count order.
Design criteria: absent values are never bare nulls. A field reads Not provided in ordinance only when the extraction corroborated the absence; otherwise it reads Not located in the indexed ordinance, which claims nothing about the law. user_provided_fields lists the values the user themselves confirmed. citations are readable references such as Tangipahoa Parish Code § 36-91; retrieval chunk identifiers are never returned as citations. stormwater_context splits into known and not_in_code, so a requirement the code is silent on is named rather than read as a zero. governing_district is the district the concept is compiled and checked against, with its basis (current, or rezone_target once the user confirmed a rezone in Site Plan Studio) and the parcel district as current_zoning_code beside it; a rezone target is also listed in districts, so never take the parcel district for the governing one.
Yield scenarios: values the user settled under one district are inherited into another district's partition when missing, with the borrowing stated per key in inherited.
CAD handoff: landxml is the native Civil 3D import format; dxf covers generic CAD; concept_png is a picture of the concept plan for people, not CAD. The response is artifact_id, filename, mime_type, download_url, and expires_at (15 minutes); geometry travels as a file, never inline. Send an Idempotency-Key header so a retried request replays the original artifact. With no compiled concept the endpoint returns 404 (no_site_plan_concept).
Concept picture disclosures: the drawing carries recorded wetland exclusion, constraint override and floodplain retention caveats, including suspended decisions. When space is limited, optional notes give way first and a long stated override reason may be shortened with a marker pointing to the full project record; the caveats remain whole. If required disclosures still cannot fit, export returns 400 (invalid_site_plan_export_request) with the refusal message. Retrying the same revision cannot resolve that refusal; transient worker failures remain 502 (export_render_failed).
How do I walk the guided site-planning interview from my own agent?
Three endpoints walk the same interview the PrecisionDocs app walks. The engine is deterministic: it picks the next question and already knows what the project settled, so you never decide what to ask next.
| Endpoint | What it does | Rate limit |
|---|---|---|
GET /v1/projects/{project_id}/interview/workflows | Guided workflows this project can walk, which one it is on, and how far in (siteplan:read) | 60/min |
GET /v1/projects/{project_id}/interview/step | The next 1-3 questions as JSON Schema, plus what is already answered or inferred (siteplan:read) | 60/min |
POST /v1/projects/{project_id}/interview/answers | Submit typed answers, get the next step back (project:create) | 30/min |
Both reads are free. No LLM call and no credit spend, so poll them rather than guessing what comes next. Only the answer endpoint writes.
Questions arrive as JSON Schema mapped from the same control the in-app card renders: types, ranges, units, and options with their labels. A default on a field is the engine's recommendation, and recommended.reason says why - offer it as a suggestion, never as a requirement.
Nothing is asked twice. answered lists what the project settled and inferred lists what the engine derived, each with the statement behind it. remaining_question_ids is the rest of the walk; complete: true with no questions means the scripted interview is finished, and completeness.missing_required names anything a concept still needs.
A rejected batch records nothing. Values are validated against the same schema server-side; a 400 invalid_interview_answer names every bad field under errors, so fix those and resend the same batch. What is recorded is the user's own decision, on the same ledger the in-app card writes - so an interview can be started in the app and finished over the API, or the other way round.
Read standing, not the workflow rows. standing always describes the walk the project is on. selected_workflow_id is null both before a workflow is chosen and for a use that has no template lane (commercial, hotel), so a null there never means the interview has not started.
How do I compile the concept plan once the interview is complete?
When the interview returns complete: true with completeness.ready: true, four endpoints turn the settled program into a drawn concept. They call the same functions the in-app Site Plan Studio calls - one engine, no second compile path - so a walk can start in the app and finish here or the other way round.
| Endpoint | What it does | Rate limit |
|---|---|---|
POST /v1/projects/{project_id}/site-plan/program/approve | Approve the development program and advance the session to Generate Concept; idempotent once approved (siteplan:write) | 30/min |
POST /v1/projects/{project_id}/site-plan/proposals | Create the generate_concept proposal (or a one-zone regeneration with zone_id); returns notes, recap and conflicts, compiles nothing yet (siteplan:write) | 10/min |
GET /v1/projects/{project_id}/site-plan/proposals | Proposals on the active planning session, newest first, with limit/offset and status_filter (siteplan:read) | 60/min |
POST /v1/projects/{project_id}/site-plan/proposals/{proposal_id}/approve | Approve = compile. Returns the applied revision and the plan's scalar metrics (siteplan:write) | 10/min |
POST /v1/projects/{project_id}/site-plan/proposals/{proposal_id}/reject | Reject, recording the optional reason (siteplan:write) | 10/min |
Every write is fenced on the session. Pass the planning_session_id the interview responses carry; a write against a session that is no longer active is 409 planning_session_conflict. state_version is optional and, when given, is a compare-and-swap token against the last response you saw. An accepted proposal write consumes that token even if the later compile blocks or fails, and the proposal-create and decision responses return the session's new state_version - send that one on the next call, and re-read the interview state after a conflict. An already-decided proposal replay consumes nothing. Send an Idempotency-Key on each write so a retry replays rather than repeats.
The writes are gated; the reads are not. Besides siteplan:write, every write requires Site Plan Studio for the organization, which comes with every paid plan (a free workspace gets it by buying credits): without it every write answers 403 site_plan_studio_not_enabled while every read in this section keeps answering 200. Single-family and subdivision plans compile through their own Studio lanes and answer 409 typology_not_supported_via_api.
A flagged proposal needs the user's reason. conflicts on a proposal names the rules the plan would break. Approving one without a reason is 422 proposal_override_reason_required; the reason is recorded in the plan's decision ledger beside the overridden findings. It is the user's own words - a client must never supply it for them.
The compile runs inline. The engine is deterministic and a 16-acre multifamily concept compiles in seconds, so approve answers with applied_revision_id, zone_failures and metrics in one call, and GET .../site-plan reports has_concept: true right after. 409 proposal_expired means the program or plan changed since the proposal - propose again. 409 proposal_blocked names a prerequisite to fix first and leaves the proposal retryable; 409 proposal_failed is a domain rejection of the compile. Re-approving an applied proposal replays (replayed: true) and never compiles twice.