Maia-analytics project API
The project API from Maia-analytics — 15 operation(s) for project.
The project API from Maia-analytics — 15 operation(s) for project.
openapi: 3.1.0
info:
title: MAIA Ah project API
description: API for MAIA application (migrated from Firebase)
version: 0.1.0
tags:
- name: project
paths:
/api/v1/project/all:
get:
tags:
- project
summary: Get All Projects
description: "Get all projects for the authenticated user.\n\nArgs:\n project_service: Project service for business logic\n user_profile: Current user's profile (for workspace role and can_edit)\n\nReturns:\n List of projects visible to the user with can_edit computed per project"
operationId: get_all_projects_api_v1_project_all_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectListResponse'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/sandbox:
post:
tags:
- project
summary: Create Sandbox Project
description: 'Create a single-click county sandbox project pre-loaded with the
county''s boundary layer.
Two-phase: this handler runs phase 1 (validate FIPS, insert PENDING
project row, return ``project_id``) synchronously and enqueues phase 2
(Neon name resolve, CREATE MATERIALIZED VIEW, register boundary layer,
flip status to READY) on the Celery `sandbox_boundary` queue. Celery is
used instead of FastAPI ``BackgroundTasks`` so a Cloud Run instance
restart between response and task completion can''t strand the project
in ``PENDING`` — the broker persists the job until a worker acks.
Workspace RBAC: rejects FIPS codes not in the user''s available
geographies (403). FIPS not loaded into the workspace''s
``workspace_counties`` table is a 404 before any project row is
inserted. Failures inside the background task mark the project
``status=FAILED`` (visible on the project page); the task itself retries
with exponential backoff before reaching that state.'
operationId: create_sandbox_project_api_v1_project_sandbox_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SandboxProjectRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/SandboxProjectResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/sandbox/from-feature:
post:
tags:
- project
summary: Create Sandbox Project From Feature
description: '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.'
operationId: create_sandbox_project_from_feature_api_v1_project_sandbox_from_feature_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddressCreateFlowRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AddressProjectResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/sandbox/from-features:
post:
tags:
- project
summary: Create Sandbox Project From Features
description: 'Create one sandbox project from several confirmed features (paste flow).
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.'
operationId: create_sandbox_project_from_features_api_v1_project_sandbox_from_features_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddressBatchCreateFlowRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AddressProjectResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/address/resolve:
post:
tags:
- project
summary: Resolve Address
description: 'Resolve free text to ranked, label-hydrated candidates for address entry.
Pre-project: bounded to ``request.county_fips`` when the caller picked a
county first, otherwise across every county the caller''s workspace has
loaded (the sandbox partition is the access control either way). A read
failure surfaces as 503 rather than the raw error, which can carry a
sandbox connection URI.'
operationId: resolve_address_api_v1_project_address_resolve_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddressResolveRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AddressResolveResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/address/resolve-many:
post:
tags:
- project
summary: Resolve Addresses Batch
description: 'Resolve a pasted handful of addresses in one batch for the paste review list.
One typed outcome per input row in input order (matched / ambiguous /
not_in_dataset / empty_input), county-bounded like the single resolve. Candidate
labels for every row are hydrated in a single read, then re-split per row. A read
failure surfaces as 503 rather than the raw error (which can carry a sandbox URI).'
operationId: resolve_addresses_batch_api_v1_project_address_resolve_many_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AddressResolveManyRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AddressResolveManyResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/address/feature-geometry:
get:
tags:
- project
summary: Get Address Feature Geometry
description: 'Drawable geometry + bbox for one resolved candidate, pre-project.
404 covers both a malformed/unknown ``feature_id`` and a row with null
geometry — either way there is nothing to draw. Read failures map to 503,
mirroring ``/address/resolve``.'
operationId: get_address_feature_geometry_api_v1_project_address_feature_geometry_get
security:
- FirebaseAuthMiddleware: []
parameters:
- name: source_table
in: query
required: true
schema:
$ref: '#/components/schemas/SourceTable'
- name: feature_id
in: query
required: true
schema:
type: string
title: Feature Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/AddressFeatureGeometryResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/api/v1/project/sandbox/warm-county:
post:
tags:
- project
summary: Warm County Boundary
description: 'Pre-warm the boundary MV for a county without creating a project.
Called from the picker the moment a user soft-selects a county, so
the Neon roundtrip (TIGER GEOID lookup + CREATE MATERIALIZED VIEW +
index) overlaps with prompt composition instead of blocking the
subsequent project-create. Idempotent — the MV is workspace-scoped
and deterministically named by ``county_fips``; concurrent warms are
serialized inside ``ensure_county_boundary_mv`` by an advisory lock
and the second caller fast-paths on the existing MV.
Workspace RBAC mirrors ``create_sandbox_project``: FIPS not in the
user''s available geographies → 403, FIPS not loaded into the
workspace''s ``workspace_counties`` table → 404, FIPS not present in
the workspace''s ``tiger_county`` table → 404. No project row is
created on any path — the warm flow is read-only at the app-DB level.'
operationId: warm_county_boundary_api_v1_project_sandbox_warm_county_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SandboxWarmCountyRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/SandboxWarmCountyResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/{project_id}:
get:
tags:
- project
summary: Get Project
description: 'Get a single project by ID.
Requires read access (ownership, workspace membership, example, or admin
view/write mode). Admin view mode forces can_edit=False; admin write mode
forces can_edit=True. Both force is_owner=False.'
operationId: get_project_api_v1_project__project_id__get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectResponse'
security:
- FirebaseAuthMiddleware: []
put:
tags:
- project
summary: Update Project
description: "Update a project's basic information (name and/or description).\n\nArgs:\n project_id: UUID of the project (validated to be owned by current user)\n project_data: Project update parameters containing optional name/description\n project_service: Project service for business logic\n\nReturns:\n Response with success status, message, and updated project data\n\nRaises:\n HTTPException: 404 if project not found or user doesn't own it\n ValueError: If no fields to update are provided"
operationId: update_project_api_v1_project__project_id__put
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdate'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdateResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
delete:
tags:
- project
summary: Delete Project
description: "Delete a project.\n\nArgs:\n project_id: UUID of the project (validated to be owned by current user)\n project_service: Project service for business logic\n\nReturns:\n Dict with success status and message\n\nRaises:\n HTTPException: 404 if project not found or user doesn't own it"
operationId: delete_project_api_v1_project__project_id__delete
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectDeleteResponse'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/{project_id}/default-view:
put:
tags:
- project
summary: Set Project Default View
description: "Set (or clear) the project's durable default view.\n\nThe default view is the view anonymous share-link visitors and first-time\nviewers land on when the URL carries no explicit ``?view=``. Settable by any\neditor (validated by the dependency).\n\nRaises:\n HTTPException: 404 if project not found or the caller can't write it,\n 400 if ``view_id`` isn't one of the project's views."
operationId: set_project_default_view_api_v1_project__project_id__default_view_put
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SetDefaultViewRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectUpdateResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/{project_id}/views/reorder:
patch:
tags:
- project
summary: Reorder Views
description: "Reorder a project's views to match the supplied id order.\n\nDeclared *before* the ``/{view_id}`` PATCH route: FastAPI matches routes in\ndeclaration order, so the literal ``reorder`` segment must register first or\n``{view_id}`` would capture it (``view_id=\"reorder\"``).\n\nArgs:\n project_id: UUID of the project (validated to be owned by current user)\n request: The full ordered list of view ids\n view_service: View service for business logic\n\nReturns:\n Response with success status and the views in their new order\n\nRaises:\n HTTPException: 404 if project not found or user doesn't own it,\n 400 if ``view_ids`` isn't a permutation of the project's views."
operationId: reorder_views_api_v1_project__project_id__views_reorder_patch
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ViewsReorderRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ViewsReorderResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/{project_id}/views/{view_id}:
patch:
tags:
- project
summary: Update View
description: "Update a single view with partial data and atomic ownership validation.\n\nArgs:\n project_id: UUID of the project (validated to be owned by current user)\n view_id: String ID of the view to update\n request: Request containing the partial view update data\n view_service: View service for business logic\n\nReturns:\n Response with success status and updated view data\n\nRaises:\n HTTPException: 404 if project/view not found or user doesn't own it,\n 409 if ``expected_version`` is stale (the view changed concurrently)."
operationId: update_view_api_v1_project__project_id__views__view_id__patch
security:
- FirebaseAuthMiddleware: []
parameters:
- name: view_id
in: path
required: true
schema:
type: string
title: View Id
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ViewUpdateRequest'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ViewUpdateResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
delete:
tags:
- project
summary: Delete View
description: "Delete a view from a project.\n\nArgs:\n project_id: UUID of the project (validated to be owned by current user)\n view_id: String ID of the view to delete\n view_service: View service for business logic\n\nReturns:\n Response with success status and message\n\nRaises:\n HTTPException: 404 if project/view not found or user doesn't own it"
operationId: delete_view_api_v1_project__project_id__views__view_id__delete
security:
- FirebaseAuthMiddleware: []
parameters:
- name: view_id
in: path
required: true
schema:
type: string
title: View Id
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ViewDeleteResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/api/v1/project/{project_id}/views:
get:
tags:
- project
summary: Get Views
description: "Get all views for a project.\n\nArgs:\n project_id: UUID of the project (validated for read access - owned or example)\n view_service: View service for business logic\n\nReturns:\n Response containing list of views\n\nRaises:\n HTTPException: 404 if project not found or user doesn't have access"
operationId: get_views_api_v1_project__project_id__views_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ViewsGetResponse'
security:
- FirebaseAuthMiddleware: []
post:
tags:
- project
summary: Create View
description: "Create a new view for a project.\n\nArgs:\n project_id: UUID of the project (validated to be owned by current user)\n request: Request containing the view to create\n view_service: View service for business logic\n\nReturns:\n Response with success status and created view data\n\nRaises:\n HTTPException: 404 if project not found or user doesn't own it"
operationId: create_view_api_v1_project__project_id__views_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ViewCreateRequest'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ViewCreateResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/{project_id}/chats:
get:
tags:
- project
summary: Get Project Chat
description: "Get the chat for a specific project.\n\nArgs:\n project_id: The ID of the project to get chat for (validated for read access).\n agent_service: The AgentService instance (dependency).\n user_repo: User repository for resolving sender display names.\n project_repo: Project repository for checking project visibility.\n user_profile: Current user's profile for internal status check.\n\nReturns:\n The chat history for the project, or None if no chat exists.\n\nRaises:\n HTTPException: 400 for validation errors.\n HTTPException: 404 if project not found or user doesn't have access.\n HTTPException: 500 for unexpected errors."
operationId: get_project_chat_api_v1_project__project_id__chats_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ChatHistoryResponseSchema'
security:
- FirebaseAuthMiddleware: []
/api/v1/project/{project_id}/share:
get:
tags:
- project
summary: Get Share Info
description: "Get share link info for a project.\n\nReturns one of three states:\n- Never shared: is_shared=False, share=None\n- Previously shared (revoked): is_shared=False, share={is_active: False, ...}\n- Currently shared: is_shared=True, share={is_active: True, ...}\n\nAlso includes workspace context (name, member count, project visibility)\nfor the share dialog. Requires write access to the project.\n\nArgs:\n project_id: UUID of the project (validated for write access)\n user_profile: Current user's profile (for workspace_id)\n share_service: ProjectShareService for share link operations\n\nReturns:\n Share info with is_shared flag, optional share details, and workspace context"
operationId: get_share_info_api_v1_project__project_id__share_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ShareInfoResponse'
security:
- FirebaseAuthMiddleware: []
post:
tags:
- project
summary: Generate Share Link
description: "Generate a share link for a project (idempotent).\n\nIf an active share link already exists, returns it. Otherwise creates\na new one with a unique token. Requires write access to the project.\n\nArgs:\n project_id: UUID of the project (validated for write access)\n share_service: ProjectShareService for share link operations\n\nReturns:\n Share link details including full URL, token, and status"
operationId: generate_share_link_api_v1_project__project_id__share_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ShareLinkResponse'
security:
- FirebaseAuthMiddleware: []
delete:
tags:
- project
summary: Revoke Share Link
description: "Revoke the active share link for a project.\n\nPermanently invalidates the current share token. Re-enabling sharing\nwill generate a new token (old links stay dead). Requires write access to the project.\n\nArgs:\n project_id: UUID of the project (validated for write access)\n share_service: ProjectShareService for share link operations\n\nReturns:\n Success confirmation\n\nRaises:\n HTTPException: 404 if no active share link exists"
operationId: revoke_share_link_api_v1_project__project_id__share_delete
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ShareRevokeResponse'
security:
- FirebaseAuthMiddleware: []
components:
schemas:
ThinkingPart:
properties:
content:
type: string
title: Content
id:
anyOf:
- type: string
- type: 'null'
title: Id
signature:
anyOf:
- type: string
- type: 'null'
title: Signature
provider_name:
anyOf:
- type: string
- type: 'null'
title: Provider Name
provider_details:
anyOf:
- additionalProperties: true
type: object
- type: 'null'
title: Provider Details
part_kind:
type: string
const: thinking
title: Part Kind
default: thinking
type: object
required:
- content
title: ThinkingPart
description: A thinking response from a model.
AudioUrl:
properties:
url:
type: string
title: Url
force_download:
anyOf:
- type: boolean
- type: string
const: allow-local
title: Force Download
default: false
vendor_metadata:
anyOf:
- additionalProperties: true
type: object
- type: 'null'
title: Vendor Metadata
kind:
type: string
const: audio-url
title: Kind
default: audio-url
media_type:
type: string
title: Media Type
description: Return the media type of the file, based on the URL or the provided `media_type`.
readOnly: true
identifier:
type: string
title: Identifier
description: 'The identifier of the file, such as a unique ID.
This identifier can be provided to the model in a message to allow it to refer to this file in a tool call argument,
and the tool can look up the file in question by iterating over the message history and finding the matching `FileUrl`.
This identifier is only automatically passed to the model when the `FileUrl` is returned by a tool.
If you''re passing the `FileUrl` as a user message, it''s up to you to include a separate text part with the identifier,
e.g. "This is file <identifier>:" preceding the `FileUrl`.
It''s also included in inline-text delimiters for providers that require inlining text documents, so the model can
distinguish multiple files.'
readOnly: true
type: object
required:
- url
- media_type
- identifier
title: AudioUrl
description: A URL to an audio file.
AddressResolveRequest:
properties:
query:
type: string
maxLength: 256
minLength: 1
title: Query
county_fips:
anyOf:
- type: string
pattern: ^\d{5}$
- type: 'null'
title: County Fips
type: object
required:
- query
title: AddressResolveRequest
description: 'Autocomplete resolve for address entry: free text (street address, place
name, or parcel number) — no project exists yet on this path. ``county_fips``
(5-digit GEOID) bounds the match to the picked county; absent, it matches
across every county the caller''s workspace has loaded.'
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
input:
title: Input
ctx:
type: object
title: Context
type: object
required:
- loc
- msg
- type
title: ValidationError
ViewModel-Input:
properties:
id:
type: string
title: Id
name:
type: string
title: Name
description:
anyOf:
- type: string
- type: 'null'
title: Description
layerFilters:
additionalProperties:
additionalProperties:
oneOf:
- $ref: '#/components/schemas/CompoundFilter-Input'
- oneOf:
- $ref: '#/components/schemas/TextCondition'
- $ref: '#/components/schemas/NumberCondition'
- $ref: '#/components/schemas/SetCondition'
- $ref: '#/components/schemas/DateCondition'
- $ref: '#/components/schemas/BooleanCondition'
discriminator:
propertyName: filterType
mapping:
boolean: '#/components/schemas/BooleanCondition'
date: '#/components/schemas/DateCondition'
number: '#/components/schemas/NumberCondition'
set: '#/components/schemas/SetCondition'
text: '#/components/schemas/TextCondition'
type: object
type: object
title: Layerfilters
mapFilters:
anyOf:
- $ref: '#/components/schemas/MapFilterModel'
- type: 'null'
layerColumnVisibility:
additionalProperties:
additionalProperties:
type: boolean
type: object
type: object
title: Layercolumnvisibility
layerColumnOrder:
additionalProperties:
items:
type: string
type: array
type: object
title: Layercolumnorder
layerColumnPinned:
additionalProperties:
items:
type: string
type: array
type: object
title: Layercolumnpinned
layerColumnSort:
additionalProperties:
items:
additionalProperties:
type: string
type: object
type: array
type: object
title: Layercolumnsort
layerColumnWidths:
additionalProperties:
additionalProperties:
type: integer
type: object
type: object
title: Layercolumnwidths
layerGroupByColumn:
additionalProperties:
anyOf:
- type: string
- type: 'null'
type: object
title: Layergroupbycolumn
layerVisibility:
anyOf:
- additionalProperties:
type: boolean
type: object
- type: 'null'
title: Layervisibility
layerViz:
anyOf:
- additionalProperties:
type: string
type: object
- type: 'null'
title: Layerviz
basemap:
anyOf:
- type: string
- type: 'null'
title: Basemap
currentLayerId:
anyOf:
- type: string
- type: 'null'
# --- truncated at 32 KB (128 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/maia-analytics/refs/heads/main/openapi/maia-analytics-project-api-openapi.yml