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

# Create Sandbox Project From Features

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

Create one sandbox project from several confirmed features (Door 1).

The multi-feature twin of ``/sandbox/from-feature``: derives one shared county
from every feature's marker before inserting the project, then enqueues a single
seed for all of them. Unknown ``feature_id`` → 404; a NULL/malformed marker →
422; features spanning more than one county → 422 (a project is scoped to one
county under RLS, so the paste must be split). ``status`` flips to ``ready`` and
the layers arrive via SSE.

``features`` is the paste flow. ``upload`` is the file-import flow, whose row
values come from the server's held inspect result rather than this request.

## Request body

- AddressBatchCreateFlowRequest — Create one sandbox project from several confirmed features. Every feature must derive to the same county (a project is scoped to one county under RLS); the server enforces this and 422s a cross-county batch. Exactly one source: ``features`` is the paste flow, which has no file behind it; ``upload`` is the file-import flow, which references a held inspect result so the server reads the user's rows from its own parse rather than from this request. ``spine="all_rows"`` is meaningful only for ``upload`` — there are no rows to seed a layer from without a file.
  - `features` AddressCreateFlowRequest[], nullable
    - `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
  - `upload` ImportUploadCommit — The held-upload half of a commit — references, never row values. The server re-reads the user's rows from its own inspect result via ``token``, so the browser's copy of the file stays display-only and the commit is not a trust boundary: no user-controlled dict keys, no reserved name collisions, no re-sanitization. ``mapped_columns`` names which held columns to carry through and is validated against the held column list.
    - `token` string, required
    - `mapped_columns` string[]
    - `matches` ImportRowMatch[], required
      - `row_index` integer, required
      - `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
      - `included` boolean
    - `dropped_row_indices` integer[]
  - `spine` 'matched_only' | 'all_rows'
  - `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)
