---
title: "Create Sandbox Project From Feature"
method: POST
path: "/api/v1/project/sandbox/from-feature"
tags: ["project"]
---

# Create Sandbox Project From Feature

`POST /api/v1/project/sandbox/from-feature`

Create a sandbox project from a confirmed resolved feature (address entry).

The create-flow inversion: unlike ``POST /sandbox`` (county picked first),
the county scope is *derived* from the matched feature before the project's
RLS role is provisioned. Two-phase — this handler derives scope, inserts the
PENDING project + role synchronously, and enqueues the async layer seed;
``status`` flips to ``ready`` and the layer arrives via SSE.

No ``available_geographies`` 403: the matched feature is in-coverage by
construction (the resolver only searched this workspace's loaded sandbox).
Unknown ``feature_id`` → 404; a county somehow not loaded → 404 before any
project row is inserted; a matched feature whose ``_county_fips`` is
NULL/malformed (a sandbox data-integrity fault, not a caller error) → 422.

## Request body

- AddressCreateFlowRequest — Create a sandbox project from a confirmed resolved feature reference. The confirm card holds ``(source_table, feature_id)`` — the server derives the county scope from the matched feature and provisions the RLS-scoped project, since the FE can't do either. This is the single-address and paste shape, which carries no uploaded columns; the file-upload flow commits through ``AddressBatchCreateFlowRequest.upload`` instead, where the columns come from the server's own held parse rather than the request body. Extras are forbidden rather than ignored: a caller still sending the retired ``columns`` field must get a 422, not have its data silently dropped on the way to a project that then looks mysteriously empty.
  - `source_table` 'parcel' | 'building' | 'place' | 'address' | 'school', required — The table a resolved feature lives in — what a caller pulls its data from, and the feature's kind. A text match in the ``address``, ``place`` or ``school`` table resolves (point-in-feature) to a ``parcel``/``building``; a ``parcel`` attribute match resolves to the parcel itself.
  - `feature_id` string, required
  - `knowledge_skill_workspace_id` string, uuid, nullable

## Response `200`

Successful Response

- AddressProjectResponse — Response for the address create-flow. ``status`` is always ``pending`` at sync-response time; the async DBOS seed flips it to ``ready``/``failed`` and the seeded layer arrives via the ``LayerStateChangedEvent`` SSE. ``county_fips`` is the 5-digit scope derived from the matched feature.
  - `project_id` string, uuid, required
  - `status` 'pending' | 'ready' | 'failed' | 'archived', required — Lifecycle status for a project. Replaces the boolean ``is_created`` whose name implied "was created" (always True once inserted) but semantically meant "is ready for the dashboard to show." Promoting to an enum lets new states (FAILED, ARCHIVED) join without contorting the boolean. - PENDING: row inserted, sandbox resources not yet materialized - READY: resources in place; visible on dashboard - FAILED: build raised mid-flight (sandbox saga rollback uses hard-delete instead, so this state is reserved for future async build paths that can't roll back atomically) - ARCHIVED: user-archived; hidden by default
  - `county_fips` string, required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.net/maia-analytics/apis/maia-api.md) · [All operations](https://skmtc.net/maia-analytics/apis/maia-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/maia-analytics/maia-api/versions/ba2b8d962161/schema)
