> For the complete documentation index, see [llms.txt](https://docs.spice.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.spice.ai/api/management-api/management/projects.md).

# Projects

Manage projects and their configurations

## List projects

> Returns projects belonging to the authenticated organization. Page with \`limit\` (default 1000, max 1000). Prefer \`after\_id\` for a stable keyset (descending \`id\`). \`offset\` is accepted for compatibility. PostgREST also caps a page at 1000 rows.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"}}}}},"paths":{"/v1/projects":{"get":{"summary":"List projects","description":"Returns projects belonging to the authenticated organization. Page with `limit` (default 1000, max 1000). Prefer `after_id` for a stable keyset (descending `id`). `offset` is accepted for compatibility. PostgREST also caps a page at 1000 rows.\n","parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000},"description":"Maximum number of projects to return"},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of projects to skip when a cursor is not supplied"},{"in":"query","name":"after_id","schema":{"type":"integer","minimum":1},"description":"Return projects with a strictly smaller id than this value."},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"400":{"description":"Invalid limit, offset, or after_id"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"}},"tags":["Projects"]}}}}
```

## Create a project

> Creates a new project in the authenticated organization.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects":{"post":{"summary":"Create a project","description":"Creates a new project in the authenticated organization.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":4,"pattern":"^[a-zA-Z0-9-]+$","description":"Project name (letters, numbers, hyphens only)"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Cloud region where a managed project will be deployed (e.g. `us-east-1`). Omit this, `cname`, and `cluster_name` to create an unattached standalone project whose placement is resolved when linked.\n"},"cname":{"type":"string","description":"**Deprecated.** Internal region CNAME (e.g. `us-east-1-prod-aws-data`). Use `region` instead. Accepted for backwards compatibility when `region` is omitted. Omit all region sources to create an unattached standalone project.\n"},"cluster_name":{"type":"string","description":"Name of the dedicated cluster to deploy to, as listed by `GET /v1/clusters`. Provide exactly one of `region` or `cluster_name`. When set, the region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Omit all region sources to create an unattached standalone project.\n"},"description":{"type":"string","description":"Project description"},"visibility":{"type":"string","enum":["public","private"],"default":"private"},"default_alerts":{"type":"boolean","default":true,"description":"Create default monitoring alerts for the new managed project. False skips initial setup. Manual alert creation remains available."},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking."},"replicas":{"type":"integer","minimum":0,"description":"Number of replicas for a managed project. Rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container. Rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project. Rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}}}}}}},"responses":{"201":{"description":"Project created successfully. The response may include a `warning` field if initial configuration could not be applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or invalid region"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity"},"409":{"description":"A project with this name already exists, or the project was deleted while its initial configuration was being written (`code` is `project_deleted`)"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"Project creation failed, including when the name-collision check could not be read","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Generic failure reason; the underlying cause stays in the server logs"}}}}}}},"tags":["Projects"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]}}}}
```

## Get a project

> Returns details for a specific project, including its configuration.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}":{"get":{"summary":"Get a project","description":"Returns details for a specific project, including its configuration.","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project details with configuration","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"created_at":{"type":"string","format":"date-time"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":"string"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":"object","additionalProperties":{"type":"string"}},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"config":{"type":"object","properties":{"spicepod":{"type":"object"},"image_tag":{"type":"string","description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime, or null when Spice operates this project's runtime updates."},"version":{"type":"string","description":"Semver range for the runtime version (e.g. 1.x, 2.x)"},"replicas":{"type":"integer"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":["number","null"]}}},"region":{"type":["string","null"],"description":"Server-resolved region; null before a standalone instance is attached."},"storage_size_gb":{"type":["number","null"]},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead."}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## Update a project

> Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/projects/{projectId}":{"put":{"summary":"Update a project","description":"Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"production_branch":{"type":"string"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"spicepod":{"oneOf":[{"type":"string","description":"YAML string of the spicepod configuration"},{"type":"object","description":"JSON object of the spicepod configuration"}],"description":"The project's Spicepod. A project connected to a GitHub repository takes its Spicepod from the repository's spicepod.yaml, so for such a project only the stored Spicepod, sent back unchanged, is accepted."},"image_tag":{"type":["string","null"],"description":"Runtime image tag for a managed project; rejected for standalone projects. A non-empty value pins the project. On stable, the pin must be a published stable runtime version. On preview, it must be a published preview or stable runtime version. On stable or preview, send null to clear a pin so deploys resolve from the channel catalog (stable - latest GA for the configured version range; preview - latest RC). null is rejected on nightly and internal channels."},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed runtime; rejected for standalone projects."},"version":{"type":"string","description":"Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects."},"replicas":{"type":"integer","minimum":0,"description":"Managed runtime replica count; rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container; rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project; rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number"}}},"region":{"type":"string","description":"Managed project region; standalone placement is resolved during attachment."},"cluster_name":{"type":"string","description":"Reassign the project to a dedicated cluster, as listed by `GET /v1/clusters`. Provide at most one of `region` or `cluster_name`. When set, region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Rejected for standalone projects, whose placement is resolved during attachment.\n"},"storage_size_gb":{"type":"number","description":"Managed runtime storage size; rejected for standalone projects."},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead. Rejected for standalone projects."}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or project ID, including placement or hosted runtime fields that do not apply to standalone projects. When `image_tag` is supplied on stable or preview, a tag that is not a published version for that channel returns `{ error: 'Invalid image_tag for stable channel', code: 'invalid_stable_image_tag' }` (or the preview analog). A `spicepod` that adds a component on a source the Spice runtime no longer loads returns code `retired_component_source`; components the project already stores on such a source may be kept, edited and renamed, and a project backed by a GitHub repository is not checked."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot mutate projects, requested replicas or resources exceed plan limits, dedicated-cluster assignment or special-org private-compute ceilings required, requested resources exceed dedicated-cluster capacity, `image_tag` was supplied outside the Enterprise plan (code: `image_tag_requires_enterprise`), or the requested runtime image is not available for the organization plan (code: `image_tag_not_allowed_for_plan`); omitting `image_tag` leaves the configured image unchanged"},"404":{"description":"Project not found"},"409":{"description":"The project's lifecycle state changed while the update was in flight — a pause, resume or restore landing concurrently — so the configuration was not saved (code: `project_state_changed`). Re-read the project before retrying: a configuration update clears `paused_at`, so repeating this request against a project that has since been paused also resumes it. Or a `spicepod` that differs from the stored one was sent for a project connected to a GitHub repository (code: `github_connected`); change spicepod.yaml in the repository instead, since nothing in that request is applied."},"500":{"description":"Internal server error"},"502":{"description":"The configuration was stored but could not be published to the spicepod registry (code: `registry_publish_failed`), so the project still deploys its previously published configuration. Retry the request."},"503":{"description":"Runtime version catalog is unavailable"}},"tags":["Projects"]}}}}
```

## Delete a project

> Deletes a project and tears down its runtime resources.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}":{"delete":{"summary":"Delete a project","description":"Deletes a project and tears down its runtime resources.","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to delete"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Project deleted successfully"},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot mutate projects"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## List forks of a project

> Returns the live projects in the organization that were forked from this project, ordered by name. Requires \`apps:read\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"}}}}},"paths":{"/v1/projects/{projectId}/forks":{"get":{"summary":"List forks of a project","description":"Returns the live projects in the organization that were forked from this project, ordered by name. Requires `apps:read`.\n","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the source project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Forks of the project","content":{"application/json":{"schema":{"type":"object","required":["forks"],"properties":{"forks":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## Fork a project

> Creates a new project from this project's configuration and records this project as its source. The fork copies the spicepod the project deploys, its linked connections, project secrets, linked organization secrets, update channel, version range, replica and executor counts, storage size, description, and tags. It does not copy API keys (new ones are generated), deployments, the GitHub repository link, the instance size, or visibility (forks are private). The fork is not deployed: deploy it with POST /v1/projects/{projectId}/deployments. Requires \`apps:write\`. The server copies secret values without returning them, so no secrets scope is needed. A source connected to a GitHub repository is read from that repository, and its spicepod is copied only when it loads no other file from the repository; to deploy a fork from a fork of the repository, fork the project in the Spice portal.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/projects/{projectId}/forks":{"post":{"summary":"Fork a project","description":"Creates a new project from this project's configuration and records this project as its source. The fork copies the spicepod the project deploys, its linked connections, project secrets, linked organization secrets, update channel, version range, replica and executor counts, storage size, description, and tags. It does not copy API keys (new ones are generated), deployments, the GitHub repository link, the instance size, or visibility (forks are private). The fork is not deployed: deploy it with POST /v1/projects/{projectId}/deployments. Requires `apps:write`. The server copies secret values without returning them, so no secrets scope is needed. A source connected to a GitHub repository is read from that repository, and its spicepod is copied only when it loads no other file from the repository; to deploy a fork from a fork of the repository, fork the project in the Spice portal.\n","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to fork"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":4,"maxLength":38,"pattern":"^[a-zA-Z0-9-]+$","description":"Name of the fork. Defaults to the source name with `-fork`, then `-fork-2` and so on, whichever is free.\n"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Region to place the fork in. Omit it and `cluster_name` to place the fork where the source runs.\n"},"cluster_name":{"type":"string","description":"Dedicated cluster to place the fork on, as listed by `GET /v1/clusters`. If `region` is also set, it must match the cluster's region.\n"},"scheduler_state_location":{"type":"string","maxLength":2048,"description":"s3:// URI for the fork's scheduler state. Required when the source spicepod sets `runtime.scheduler.state_location`, and must not overlap it: two schedulers cannot share a location.\n"},"default_alerts":{"type":"boolean","default":true,"description":"Create default monitoring alerts for the new managed fork. False skips initial setup. Manual alert creation remains available."}}}}}},"responses":{"201":{"description":"Project forked. It has not been deployed yet.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProjectWithConfig"},{"type":"object","required":["shared_state"],"properties":{"shared_state":{"type":"array","description":"Settings copied as they are that name external state the source also uses: a replication slot or Kafka consumer group the spicepod names, or a snapshot location. Change them in the fork's spicepod before deploying it if the two projects should not share that state.\n","items":{"type":"object","required":["component","setting","value"],"properties":{"component":{"type":"string","description":"The dataset, or `snapshots`"},"setting":{"type":"string"},"value":{"type":"string"}}}}}}]}}}},"400":{"description":"Invalid project ID or request body, an unknown region or cluster, no `region` or `cluster_name` set for a source whose placement a fork cannot reuse (a BYOC cluster, a self-hosted runtime, or a region or cluster that is no longer available; `code: fork_placement_required`), or a missing or unusable `scheduler_state_location` (`code: scheduler_state_location_required`, `scheduler_state_location_unsupported`, `scheduler_state_location_overlaps_source`, or `scheduler_state_location_not_applicable`).\n"},"401":{"description":"Unauthorized"},"402":{"description":"The source runs models on hosted AI credits, which the organization has used up (`code: ai_credits_exhausted`).\n"},"403":{"description":"Insufficient scope, a viewer role, or the source runs more replicas or executors than the plan or the dedicated cluster allows\n"},"404":{"description":"Project or organization not found"},"409":{"description":"A project with the requested name already exists, another project creation is in progress, or every default name is taken (`code: fork_name_unavailable`)\n"},"413":{"description":"Request body too large"},"422":{"description":"The source has no spicepod (`code: fork_source_has_no_spicepod`), its spicepod is not valid (`code: fork_source_spicepod_invalid`), or its spicepod loads files from its GitHub repository besides spicepod.yaml (`code: fork_source_loads_repository_files`)\n"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"The fork could not be created. When the failure comes after the project row is created, the fork is deleted again with its spicepod, secrets, and organization secret links. If the project itself cannot be deleted, the response has `code: fork_rollback_failed` and names the partially created project in `project` (`id` and `name`). Delete that project, by `project.id`, before reusing its name.\n"},"502":{"description":"The source is connected to a GitHub repository, and its spicepod could not be read from it (`code: fork_source_repository_unreadable`)\n"}},"tags":["Projects"]}}}}
```

## Pause a project

> Pauses the project's running spicepod. The runtime is torn down and the\
> spicepod record is marked as paused. Settings and configuration are\
> preserved. Resume with POST /v1/projects/{projectId}/resume. Requires\
> \`apps:write\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}/pause":{"post":{"summary":"Pause a project","description":"Pauses the project's running spicepod. The runtime is torn down and the\nspicepod record is marked as paused. Settings and configuration are\npreserved. Resume with POST /v1/projects/{projectId}/resume. Requires\n`apps:write`.\n","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to pause"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project paused","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp when the project was paused"}}}}}},"400":{"description":"Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"Project is already paused","content":{"application/json":{"schema":{"type":"object","required":["error","paused_at"],"properties":{"error":{"type":"string"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp of the existing pause"}}}}}},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## Resume a paused project

> Clears the paused state on the project's spicepod and creates a new\
> deployment to bring the runtime back up. A project connected to a\
> GitHub repository deploys spicepod.yaml from its root directory on its\
> production branch, read when the project resumes. Requires\
> \`apps:write\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}/resume":{"post":{"summary":"Resume a paused project","description":"Clears the paused state on the project's spicepod and creates a new\ndeployment to bring the runtime back up. A project connected to a\nGitHub repository deploys spicepod.yaml from its root directory on its\nproduction branch, read when the project resumes. Requires\n`apps:write`.\n","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to resume"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project resumed, deployment created","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at","deployment_id"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":["string","null"],"format":"date-time","description":"Always null after a successful resume"},"deployment_id":{"type":"integer","description":"ID of the deployment created to bring the runtime back up"}}}}}},"400":{"description":"Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or spicepod configuration is invalid. For a project connected to a GitHub repository, also returned when the root directory has no spicepod.yaml or it is invalid or too large (`code: spicepod_not_found`, `spicepod_invalid` or `spicepod_too_large`). The project stays paused.\n"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, or the org's GitHub App installation cannot read the connected repository (`code: github_unauthorized` or `github_forbidden`). The project stays paused.\n"},"404":{"description":"Project not found, or the connected GitHub repository or its production branch was not found (`code: github_not_found`). The project stays paused.\n"},"409":{"description":"Project is not paused"},"422":{"description":"No runtime version in the catalog matches this project’s version range (`code: version_no_match`), or the spicepod uses version v1beta1, which the Spice 2 runtime cannot load (`code: spicepod_version_unsupported`).\n"},"429":{"description":"GitHub rate-limited the read of the connected repository (`code: github_rate_limited`). The project stays paused.\n"},"500":{"description":"Internal server error"},"502":{"description":"GitHub did not answer while the connected repository was read (`code: github_unavailable`). The project stays paused.\n"},"503":{"description":"The runtime version catalog is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.\n"}},"tags":["Projects"]}}}}
```

## Connect a project to a GitHub repository

> Connects the project to a repository in the organization's GitHub\
> account, through the organization's Spice Cloud GitHub App\
> installation. The next deployment reads spicepod.yaml (or\
> spicepod.yml) from \`root\_directory\` on \`production\_branch\`.\
> Connecting sets the project description to the repository\
> description. Connecting again replaces the repository, root directory\
> and production branch.\
> Requires \`apps:write\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}/repository":{"put":{"summary":"Connect a project to a GitHub repository","description":"Connects the project to a repository in the organization's GitHub\naccount, through the organization's Spice Cloud GitHub App\ninstallation. The next deployment reads spicepod.yaml (or\nspicepod.yml) from `root_directory` on `production_branch`.\nConnecting sets the project description to the repository\ndescription. Connecting again replaces the repository, root directory\nand production branch.\nRequires `apps:write`.\n","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to connect"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["repository"],"properties":{"repository":{"type":"string","description":"Repository in the organization's GitHub account, as `name` or `owner/name`. The owner must be the organization's GitHub account."},"root_directory":{"type":["string","null"],"maxLength":512,"description":"Directory inside the repository that contains spicepod.yaml. Omit it, or send null or an empty string, for the repository root."},"production_branch":{"type":"string","maxLength":255,"description":"Branch that deployments read. Defaults to the repository's default branch. A branch that GitHub renamed is stored under its new name."}}}}}},"responses":{"200":{"description":"Project connected","content":{"application/json":{"schema":{"type":"object","required":["project_id","repository","repository_id","root_directory","production_branch","connected_at","has_spicepod"],"properties":{"project_id":{"type":"integer","description":"The project ID"},"repository":{"type":"string","description":"The repository as `owner/name`"},"repository_id":{"type":"integer","description":"The GitHub repository ID"},"root_directory":{"type":["string","null"],"description":"The stored root directory. Null is the repository root."},"production_branch":{"type":"string","description":"The branch that deployments read"},"connected_at":{"type":"string","format":"date-time","description":"When the project was connected"},"has_spicepod":{"type":"boolean","description":"True when spicepod.yaml or spicepod.yml exists in the root directory on the production branch. A deployment fails until it does."}}}}}},"400":{"description":"Invalid project ID or request body, a repository outside the organization's GitHub account (code: `invalid_repository`), or a root directory outside the repository (code: `invalid_root_directory`)"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, or viewer role cannot mutate projects"},"404":{"description":"Project not found, or the organization's GitHub App installation cannot read the repository (code: `repository_not_found`)"},"409":{"description":"The organization has no usable GitHub App installation (code: `github_not_connected`), or another project already deploys this repository and root directory (code: `root_directory_conflict`)"},"413":{"description":"Request body too large"},"422":{"description":"The repository has no branch named `production_branch` (code: `branch_not_found`), the project has no spicepod configuration (code: `spicepod_missing`), or the organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"GitHub did not answer (code: `github_unavailable`)"}},"tags":["Projects"]}}}}
```

## Get project metrics

> Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}/metrics":{"get":{"summary":"Get project metrics","description":"Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"window","required":false,"schema":{"type":"string"},"description":"Prometheus-style duration (e.g. `5m`, `1h`, `30s`). When provided, counter metrics are returned as rates over the window instead of raw cumulative values. For example, `rows_ingested` returns average rows per second with a window, or total cumulative rows without one."},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Resource and ingestion metrics. Missing fields indicate no data is currently available. Individual metric queries may fail independently; partial results are returned when possible.","content":{"application/json":{"schema":{"type":"object","properties":{"ingestion":{"type":"object","description":"Aggregated data ingestion metrics","properties":{"rows_ingested":{"type":"number","description":"Total rows ingested (without window) or rows per second (with window)"},"bytes_ingested":{"type":"number","description":"Total bytes ingested (without window) or bytes per second (with window)"}}},"cluster":{"type":"object","description":"Cluster metrics for the project","properties":{"active_executors_count":{"type":"number","description":"Number of active executors registered with the scheduler"}}},"metrics":{"type":"object","description":"Resource metrics keyed by pod name","additionalProperties":{"type":"object","properties":{"cpu_usage_percent":{"type":"number","description":"CPU usage as a percentage (0-100)"},"memory_usage_bytes":{"type":"number","description":"Memory usage in bytes"},"disk_read_bytes":{"type":"number","description":"Disk bytes read per second (with window) or cumulative (without window)"},"disk_read_operations":{"type":"number","description":"Disk read operations per second (with window) or cumulative count (without window)"},"disk_write_bytes":{"type":"number","description":"Disk bytes written per second (with window) or cumulative (without window)"},"disk_write_operations":{"type":"number","description":"Disk write operations per second (with window) or cumulative count (without window)"}}}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or organization not found"},"422":{"description":"Organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"Metrics service unavailable"}},"tags":["Projects"]}}}}
```

## List project instances

> Lists the runtime instances of a deployed project, newest first. A managed or BYOC project lists its Spicepod instances. A standalone project lists its Cloud Connect runtime. Read the logs of an instance with \`GET /v1/projects/{projectId}/instances/{instanceName}/logs\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"ProjectInstance":{"type":"object","required":["name","status","started_at"],"properties":{"name":{"type":"string","description":"The instance name: a Spicepod instance (pod) name, or a Cloud Connect runtime id. Pass it as `instanceName` to read its logs."},"status":{"type":"string","enum":["deploying","loading","ready","unhealthy","terminating","failed","failed_to_start","online","offline"],"description":"A Spicepod instance is `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, or `failed_to_start`. A Cloud Connect runtime is `online` or `offline`."},"started_at":{"type":["string","null"],"format":"date-time","description":"When the Spicepod instance started, or when the Cloud Connect runtime enrolled."}}},"ProjectRuntimeError":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["project_not_deployed","project_paused"]}}}}},"paths":{"/v1/projects/{projectId}/instances":{"get":{"summary":"List project instances","description":"Lists the runtime instances of a deployed project, newest first. A managed or BYOC project lists its Spicepod instances. A standalone project lists its Cloud Connect runtime. Read the logs of an instance with `GET /v1/projects/{projectId}/instances/{instanceName}/logs`.","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"The project's instances","content":{"application/json":{"schema":{"type":"object","required":["instances"],"properties":{"instances":{"type":"array","items":{"$ref":"#/components/schemas/ProjectInstance"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"The project has no running instances. `code` is `project_not_deployed` when a managed or BYOC project was never deployed, or `project_paused` when the project is paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRuntimeError"}}}},"422":{"description":"The project is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"The project runtime did not answer"}},"tags":["Projects"]}}}}
```

## Get project instance logs

> Returns the most recent log lines of one instance of a deployed project, oldest first, without color codes. The org's plan sets the largest \`tail\`. List the instance names with \`GET /v1/projects/{projectId}/instances\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/projects/{projectId}/instances/{instanceName}/logs":{"get":{"summary":"Get project instance logs","description":"Returns the most recent log lines of one instance of a deployed project, oldest first, without color codes. The org's plan sets the largest `tail`. List the instance names with `GET /v1/projects/{projectId}/instances`.","parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"instanceName","required":true,"schema":{"type":"string"},"description":"The instance name from `GET /v1/projects/{projectId}/instances`"},{"in":"query","name":"tail","required":false,"schema":{"type":"integer","minimum":1,"default":100},"description":"How many of the most recent lines to return"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"The instance's most recent log lines","content":{"application/json":{"schema":{"type":"object","required":["instance","tail","lines"],"properties":{"instance":{"type":"string","description":"The instance name"},"tail":{"type":"integer","description":"The number of lines asked for"},"lines":{"type":"array","items":{"type":"string"},"description":"Log lines, oldest first"}}}}}},"400":{"description":"Invalid project ID or `tail`"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope (`code` is `insufficient_scope`), or `tail` is larger than the org's plan allows (`code` is `plan_limit`, and `max_tail` is the largest allowed `tail`).","content":{"application/json":{"schema":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"max_tail":{"type":"integer"}}}}}},"404":{"description":"The project was not found, or the instance is not one of the project's instances (`code` is `instance_not_found`)."},"409":{"description":"The logs cannot be read in the project's current state. `code` is `project_not_deployed` when the project was never deployed, `project_paused` when it is paused, or `instance_offline` when the Cloud Connect runtime is offline and does not answer.","content":{"application/json":{"schema":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["project_not_deployed","project_paused","instance_offline"]}}}}}},"422":{"description":"The project is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"The project runtime did not answer"}},"tags":["Projects"]}}}}
```

## List projects

> Legacy alias of \`/v1/projects\` — projects were previously called apps. Returns projects belonging to the authenticated organization. Page with \`limit\` (default 1000, max 1000). Prefer \`after\_id\` for a stable keyset (descending \`id\`). \`offset\` is accepted for compatibility. PostgREST also caps a page at 1000 rows.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"}}}}},"paths":{"/v1/apps":{"get":{"summary":"List projects","description":"Legacy alias of `/v1/projects` — projects were previously called apps. Returns projects belonging to the authenticated organization. Page with `limit` (default 1000, max 1000). Prefer `after_id` for a stable keyset (descending `id`). `offset` is accepted for compatibility. PostgREST also caps a page at 1000 rows.","parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000},"description":"Maximum number of projects to return"},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of projects to skip when a cursor is not supplied"},{"in":"query","name":"after_id","schema":{"type":"integer","minimum":1},"description":"Return projects with a strictly smaller id than this value."},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"object","properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"400":{"description":"Invalid limit, offset, or after_id"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"}},"tags":["Projects"],"deprecated":true}}}}
```

## Create a project

> Legacy alias of \`/v1/projects\` — projects were previously called apps. Creates a new project in the authenticated organization.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps":{"post":{"summary":"Create a project","description":"Legacy alias of `/v1/projects` — projects were previously called apps. Creates a new project in the authenticated organization.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":4,"pattern":"^[a-zA-Z0-9-]+$","description":"Project name (letters, numbers, hyphens only)"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Cloud region where a managed project will be deployed (e.g. `us-east-1`). Omit this, `cname`, and `cluster_name` to create an unattached standalone project whose placement is resolved when linked.\n"},"cname":{"type":"string","description":"**Deprecated.** Internal region CNAME (e.g. `us-east-1-prod-aws-data`). Use `region` instead. Accepted for backwards compatibility when `region` is omitted. Omit all region sources to create an unattached standalone project.\n"},"cluster_name":{"type":"string","description":"Name of the dedicated cluster to deploy to, as listed by `GET /v1/clusters`. Provide exactly one of `region` or `cluster_name`. When set, the region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Omit all region sources to create an unattached standalone project.\n"},"description":{"type":"string","description":"Project description"},"visibility":{"type":"string","enum":["public","private"],"default":"private"},"default_alerts":{"type":"boolean","default":true,"description":"Create default monitoring alerts for the new managed project. False skips initial setup. Manual alert creation remains available."},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking."},"replicas":{"type":"integer","minimum":0,"description":"Number of replicas for a managed project. Rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container. Rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project. Rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}}}}}}},"responses":{"201":{"description":"Project created successfully. The response may include a `warning` field if initial configuration could not be applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or invalid region"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity"},"409":{"description":"A project with this name already exists, or the project was deleted while its initial configuration was being written (`code` is `project_deleted`)"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"Project creation failed, including when the name-collision check could not be read","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Generic failure reason; the underlying cause stays in the server logs"}}}}}}},"tags":["Projects"],"parameters":[{"$ref":"#/components/parameters/OrgName"}],"deprecated":true}}}}
```

## Get a project

> Legacy alias of \`/v1/projects/{projectId}\` — projects were previously called apps. Returns details for a specific project, including its configuration.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}":{"get":{"summary":"Get a project","description":"Legacy alias of `/v1/projects/{projectId}` — projects were previously called apps. Returns details for a specific project, including its configuration.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project details with configuration","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"created_at":{"type":"string","format":"date-time"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":"string"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":"object","additionalProperties":{"type":"string"}},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"config":{"type":"object","properties":{"spicepod":{"type":"object"},"image_tag":{"type":"string","description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime, or null when Spice operates this project's runtime updates."},"version":{"type":"string","description":"Semver range for the runtime version (e.g. 1.x, 2.x)"},"replicas":{"type":"integer"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":["number","null"]}}},"region":{"type":["string","null"],"description":"Server-resolved region; null before a standalone instance is attached."},"storage_size_gb":{"type":["number","null"]},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead."}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true}}}}
```

## Update a project

> Legacy alias of \`/v1/projects/{projectId}\` — projects were previously called apps. Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/apps/{appId}":{"put":{"summary":"Update a project","description":"Legacy alias of `/v1/projects/{projectId}` — projects were previously called apps. Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"production_branch":{"type":"string"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"spicepod":{"oneOf":[{"type":"string","description":"YAML string of the spicepod configuration"},{"type":"object","description":"JSON object of the spicepod configuration"}],"description":"The project's Spicepod. A project connected to a GitHub repository takes its Spicepod from the repository's spicepod.yaml, so for such a project only the stored Spicepod, sent back unchanged, is accepted."},"image_tag":{"type":["string","null"],"description":"Runtime image tag for a managed project; rejected for standalone projects. A non-empty value pins the project. On stable, the pin must be a published stable runtime version. On preview, it must be a published preview or stable runtime version. On stable or preview, send null to clear a pin so deploys resolve from the channel catalog (stable - latest GA for the configured version range; preview - latest RC). null is rejected on nightly and internal channels."},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed runtime; rejected for standalone projects."},"version":{"type":"string","description":"Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects."},"replicas":{"type":"integer","minimum":0,"description":"Managed runtime replica count; rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container; rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project; rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number"}}},"region":{"type":"string","description":"Managed project region; standalone placement is resolved during attachment."},"cluster_name":{"type":"string","description":"Reassign the project to a dedicated cluster, as listed by `GET /v1/clusters`. Provide at most one of `region` or `cluster_name`. When set, region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Rejected for standalone projects, whose placement is resolved during attachment.\n"},"storage_size_gb":{"type":"number","description":"Managed runtime storage size; rejected for standalone projects."},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead. Rejected for standalone projects."}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or project ID, including placement or hosted runtime fields that do not apply to standalone projects. When `image_tag` is supplied on stable or preview, a tag that is not a published version for that channel returns `{ error: 'Invalid image_tag for stable channel', code: 'invalid_stable_image_tag' }` (or the preview analog). A `spicepod` that adds a component on a source the Spice runtime no longer loads returns code `retired_component_source`; components the project already stores on such a source may be kept, edited and renamed, and a project backed by a GitHub repository is not checked."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot mutate projects, requested replicas or resources exceed plan limits, dedicated-cluster assignment or special-org private-compute ceilings required, requested resources exceed dedicated-cluster capacity, `image_tag` was supplied outside the Enterprise plan (code: `image_tag_requires_enterprise`), or the requested runtime image is not available for the organization plan (code: `image_tag_not_allowed_for_plan`); omitting `image_tag` leaves the configured image unchanged"},"404":{"description":"Project not found"},"409":{"description":"The project's lifecycle state changed while the update was in flight — a pause, resume or restore landing concurrently — so the configuration was not saved (code: `project_state_changed`). Re-read the project before retrying: a configuration update clears `paused_at`, so repeating this request against a project that has since been paused also resumes it. Or a `spicepod` that differs from the stored one was sent for a project connected to a GitHub repository (code: `github_connected`); change spicepod.yaml in the repository instead, since nothing in that request is applied."},"500":{"description":"Internal server error"},"502":{"description":"The configuration was stored but could not be published to the spicepod registry (code: `registry_publish_failed`), so the project still deploys its previously published configuration. Retry the request."},"503":{"description":"Runtime version catalog is unavailable"}},"tags":["Projects"],"deprecated":true}}}}
```

## Delete a project

> Legacy alias of \`/v1/projects/{projectId}\` — projects were previously called apps. Deletes a project and tears down its runtime resources.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}":{"delete":{"summary":"Delete a project","description":"Legacy alias of `/v1/projects/{projectId}` — projects were previously called apps. Deletes a project and tears down its runtime resources.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to delete"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Project deleted successfully"},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot mutate projects"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true}}}}
```

## List forks of a project

> Legacy alias of \`/v1/projects/{projectId}/forks\` — projects were previously called apps. Returns the live projects in the organization that were forked from this project, ordered by name. Requires \`apps:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"}}}}},"paths":{"/v1/apps/{appId}/forks":{"get":{"summary":"List forks of a project","description":"Legacy alias of `/v1/projects/{projectId}/forks` — projects were previously called apps. Returns the live projects in the organization that were forked from this project, ordered by name. Requires `apps:read`.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the source project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Forks of the project","content":{"application/json":{"schema":{"type":"object","required":["forks"],"properties":{"forks":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true}}}}
```

## Fork a project

> Legacy alias of \`/v1/projects/{projectId}/forks\` — projects were previously called apps. Creates a new project from this project's configuration and records this project as its source. The fork copies the spicepod the project deploys, its linked connections, project secrets, linked organization secrets, update channel, version range, replica and executor counts, storage size, description, and tags. It does not copy API keys (new ones are generated), deployments, the GitHub repository link, the instance size, or visibility (forks are private). The fork is not deployed: deploy it with POST /v1/projects/{projectId}/deployments. Requires \`apps:write\`. The server copies secret values without returning them, so no secrets scope is needed. A source connected to a GitHub repository is read from that repository, and its spicepod is copied only when it loads no other file from the repository; to deploy a fork from a fork of the repository, fork the project in the Spice portal.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/apps/{appId}/forks":{"post":{"summary":"Fork a project","description":"Legacy alias of `/v1/projects/{projectId}/forks` — projects were previously called apps. Creates a new project from this project's configuration and records this project as its source. The fork copies the spicepod the project deploys, its linked connections, project secrets, linked organization secrets, update channel, version range, replica and executor counts, storage size, description, and tags. It does not copy API keys (new ones are generated), deployments, the GitHub repository link, the instance size, or visibility (forks are private). The fork is not deployed: deploy it with POST /v1/projects/{projectId}/deployments. Requires `apps:write`. The server copies secret values without returning them, so no secrets scope is needed. A source connected to a GitHub repository is read from that repository, and its spicepod is copied only when it loads no other file from the repository; to deploy a fork from a fork of the repository, fork the project in the Spice portal.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to fork"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":4,"maxLength":38,"pattern":"^[a-zA-Z0-9-]+$","description":"Name of the fork. Defaults to the source name with `-fork`, then `-fork-2` and so on, whichever is free.\n"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Region to place the fork in. Omit it and `cluster_name` to place the fork where the source runs.\n"},"cluster_name":{"type":"string","description":"Dedicated cluster to place the fork on, as listed by `GET /v1/clusters`. If `region` is also set, it must match the cluster's region.\n"},"scheduler_state_location":{"type":"string","maxLength":2048,"description":"s3:// URI for the fork's scheduler state. Required when the source spicepod sets `runtime.scheduler.state_location`, and must not overlap it: two schedulers cannot share a location.\n"},"default_alerts":{"type":"boolean","default":true,"description":"Create default monitoring alerts for the new managed fork. False skips initial setup. Manual alert creation remains available."}}}}}},"responses":{"201":{"description":"Project forked. It has not been deployed yet.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProjectWithConfig"},{"type":"object","required":["shared_state"],"properties":{"shared_state":{"type":"array","description":"Settings copied as they are that name external state the source also uses: a replication slot or Kafka consumer group the spicepod names, or a snapshot location. Change them in the fork's spicepod before deploying it if the two projects should not share that state.\n","items":{"type":"object","required":["component","setting","value"],"properties":{"component":{"type":"string","description":"The dataset, or `snapshots`"},"setting":{"type":"string"},"value":{"type":"string"}}}}}}]}}}},"400":{"description":"Invalid project ID or request body, an unknown region or cluster, no `region` or `cluster_name` set for a source whose placement a fork cannot reuse (a BYOC cluster, a self-hosted runtime, or a region or cluster that is no longer available; `code: fork_placement_required`), or a missing or unusable `scheduler_state_location` (`code: scheduler_state_location_required`, `scheduler_state_location_unsupported`, `scheduler_state_location_overlaps_source`, or `scheduler_state_location_not_applicable`).\n"},"401":{"description":"Unauthorized"},"402":{"description":"The source runs models on hosted AI credits, which the organization has used up (`code: ai_credits_exhausted`).\n"},"403":{"description":"Insufficient scope, a viewer role, or the source runs more replicas or executors than the plan or the dedicated cluster allows\n"},"404":{"description":"Project or organization not found"},"409":{"description":"A project with the requested name already exists, another project creation is in progress, or every default name is taken (`code: fork_name_unavailable`)\n"},"413":{"description":"Request body too large"},"422":{"description":"The source has no spicepod (`code: fork_source_has_no_spicepod`), its spicepod is not valid (`code: fork_source_spicepod_invalid`), or its spicepod loads files from its GitHub repository besides spicepod.yaml (`code: fork_source_loads_repository_files`)\n"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"The fork could not be created. When the failure comes after the project row is created, the fork is deleted again with its spicepod, secrets, and organization secret links. If the project itself cannot be deleted, the response has `code: fork_rollback_failed` and names the partially created project in `project` (`id` and `name`). Delete that project, by `project.id`, before reusing its name.\n"},"502":{"description":"The source is connected to a GitHub repository, and its spicepod could not be read from it (`code: fork_source_repository_unreadable`)\n"}},"tags":["Projects"],"deprecated":true}}}}
```

## Pause a project

> Legacy alias of \`/v1/projects/{projectId}/pause\` — projects were previously called apps. Pauses the project's running spicepod. The runtime is torn down and the\
> spicepod record is marked as paused. Settings and configuration are\
> preserved. Resume with POST /v1/projects/{projectId}/resume. Requires\
> \`apps:write\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}/pause":{"post":{"summary":"Pause a project","description":"Legacy alias of `/v1/projects/{projectId}/pause` — projects were previously called apps. Pauses the project's running spicepod. The runtime is torn down and the\nspicepod record is marked as paused. Settings and configuration are\npreserved. Resume with POST /v1/projects/{projectId}/resume. Requires\n`apps:write`.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to pause"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project paused","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp when the project was paused"}}}}}},"400":{"description":"Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"Project is already paused","content":{"application/json":{"schema":{"type":"object","required":["error","paused_at"],"properties":{"error":{"type":"string"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp of the existing pause"}}}}}},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true}}}}
```

## Resume a paused project

> Legacy alias of \`/v1/projects/{projectId}/resume\` — projects were previously called apps. Clears the paused state on the project's spicepod and creates a new\
> deployment to bring the runtime back up. A project connected to a\
> GitHub repository deploys spicepod.yaml from its root directory on its\
> production branch, read when the project resumes. Requires\
> \`apps:write\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}/resume":{"post":{"summary":"Resume a paused project","description":"Legacy alias of `/v1/projects/{projectId}/resume` — projects were previously called apps. Clears the paused state on the project's spicepod and creates a new\ndeployment to bring the runtime back up. A project connected to a\nGitHub repository deploys spicepod.yaml from its root directory on its\nproduction branch, read when the project resumes. Requires\n`apps:write`.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to resume"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project resumed, deployment created","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at","deployment_id"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":["string","null"],"format":"date-time","description":"Always null after a successful resume"},"deployment_id":{"type":"integer","description":"ID of the deployment created to bring the runtime back up"}}}}}},"400":{"description":"Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or spicepod configuration is invalid. For a project connected to a GitHub repository, also returned when the root directory has no spicepod.yaml or it is invalid or too large (`code: spicepod_not_found`, `spicepod_invalid` or `spicepod_too_large`). The project stays paused.\n"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, or the org's GitHub App installation cannot read the connected repository (`code: github_unauthorized` or `github_forbidden`). The project stays paused.\n"},"404":{"description":"Project not found, or the connected GitHub repository or its production branch was not found (`code: github_not_found`). The project stays paused.\n"},"409":{"description":"Project is not paused"},"422":{"description":"No runtime version in the catalog matches this project’s version range (`code: version_no_match`), or the spicepod uses version v1beta1, which the Spice 2 runtime cannot load (`code: spicepod_version_unsupported`).\n"},"429":{"description":"GitHub rate-limited the read of the connected repository (`code: github_rate_limited`). The project stays paused.\n"},"500":{"description":"Internal server error"},"502":{"description":"GitHub did not answer while the connected repository was read (`code: github_unavailable`). The project stays paused.\n"},"503":{"description":"The runtime version catalog is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.\n"}},"tags":["Projects"],"deprecated":true}}}}
```

## Connect a project to a GitHub repository

> Legacy alias of \`/v1/projects/{projectId}/repository\` — projects were previously called apps. Connects the project to a repository in the organization's GitHub\
> account, through the organization's Spice Cloud GitHub App\
> installation. The next deployment reads spicepod.yaml (or\
> spicepod.yml) from \`root\_directory\` on \`production\_branch\`.\
> Connecting sets the project description to the repository\
> description. Connecting again replaces the repository, root directory\
> and production branch.\
> Requires \`apps:write\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}/repository":{"put":{"summary":"Connect a project to a GitHub repository","description":"Legacy alias of `/v1/projects/{projectId}/repository` — projects were previously called apps. Connects the project to a repository in the organization's GitHub\naccount, through the organization's Spice Cloud GitHub App\ninstallation. The next deployment reads spicepod.yaml (or\nspicepod.yml) from `root_directory` on `production_branch`.\nConnecting sets the project description to the repository\ndescription. Connecting again replaces the repository, root directory\nand production branch.\nRequires `apps:write`.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to connect"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["repository"],"properties":{"repository":{"type":"string","description":"Repository in the organization's GitHub account, as `name` or `owner/name`. The owner must be the organization's GitHub account."},"root_directory":{"type":["string","null"],"maxLength":512,"description":"Directory inside the repository that contains spicepod.yaml. Omit it, or send null or an empty string, for the repository root."},"production_branch":{"type":"string","maxLength":255,"description":"Branch that deployments read. Defaults to the repository's default branch. A branch that GitHub renamed is stored under its new name."}}}}}},"responses":{"200":{"description":"Project connected","content":{"application/json":{"schema":{"type":"object","required":["project_id","repository","repository_id","root_directory","production_branch","connected_at","has_spicepod"],"properties":{"project_id":{"type":"integer","description":"The project ID"},"repository":{"type":"string","description":"The repository as `owner/name`"},"repository_id":{"type":"integer","description":"The GitHub repository ID"},"root_directory":{"type":["string","null"],"description":"The stored root directory. Null is the repository root."},"production_branch":{"type":"string","description":"The branch that deployments read"},"connected_at":{"type":"string","format":"date-time","description":"When the project was connected"},"has_spicepod":{"type":"boolean","description":"True when spicepod.yaml or spicepod.yml exists in the root directory on the production branch. A deployment fails until it does."}}}}}},"400":{"description":"Invalid project ID or request body, a repository outside the organization's GitHub account (code: `invalid_repository`), or a root directory outside the repository (code: `invalid_root_directory`)"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, or viewer role cannot mutate projects"},"404":{"description":"Project not found, or the organization's GitHub App installation cannot read the repository (code: `repository_not_found`)"},"409":{"description":"The organization has no usable GitHub App installation (code: `github_not_connected`), or another project already deploys this repository and root directory (code: `root_directory_conflict`)"},"413":{"description":"Request body too large"},"422":{"description":"The repository has no branch named `production_branch` (code: `branch_not_found`), the project has no spicepod configuration (code: `spicepod_missing`), or the organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"GitHub did not answer (code: `github_unavailable`)"}},"tags":["Projects"],"deprecated":true}}}}
```

## Get project metrics

> Legacy alias of \`/v1/projects/{projectId}/metrics\` — projects were previously called apps. Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}/metrics":{"get":{"summary":"Get project metrics","description":"Legacy alias of `/v1/projects/{projectId}/metrics` — projects were previously called apps. Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"window","required":false,"schema":{"type":"string"},"description":"Prometheus-style duration (e.g. `5m`, `1h`, `30s`). When provided, counter metrics are returned as rates over the window instead of raw cumulative values. For example, `rows_ingested` returns average rows per second with a window, or total cumulative rows without one."},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Resource and ingestion metrics. Missing fields indicate no data is currently available. Individual metric queries may fail independently; partial results are returned when possible.","content":{"application/json":{"schema":{"type":"object","properties":{"ingestion":{"type":"object","description":"Aggregated data ingestion metrics","properties":{"rows_ingested":{"type":"number","description":"Total rows ingested (without window) or rows per second (with window)"},"bytes_ingested":{"type":"number","description":"Total bytes ingested (without window) or bytes per second (with window)"}}},"cluster":{"type":"object","description":"Cluster metrics for the project","properties":{"active_executors_count":{"type":"number","description":"Number of active executors registered with the scheduler"}}},"metrics":{"type":"object","description":"Resource metrics keyed by pod name","additionalProperties":{"type":"object","properties":{"cpu_usage_percent":{"type":"number","description":"CPU usage as a percentage (0-100)"},"memory_usage_bytes":{"type":"number","description":"Memory usage in bytes"},"disk_read_bytes":{"type":"number","description":"Disk bytes read per second (with window) or cumulative (without window)"},"disk_read_operations":{"type":"number","description":"Disk read operations per second (with window) or cumulative count (without window)"},"disk_write_bytes":{"type":"number","description":"Disk bytes written per second (with window) or cumulative (without window)"},"disk_write_operations":{"type":"number","description":"Disk write operations per second (with window) or cumulative count (without window)"}}}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or organization not found"},"422":{"description":"Organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"Metrics service unavailable"}},"tags":["Projects"],"deprecated":true}}}}
```

## List project instances

> Legacy alias of \`/v1/projects/{projectId}/instances\` — projects were previously called apps. Lists the runtime instances of a deployed project, newest first. A managed or BYOC project lists its Spicepod instances. A standalone project lists its Cloud Connect runtime. Read the logs of an instance with \`GET /v1/projects/{projectId}/instances/{instanceName}/logs\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"ProjectInstance":{"type":"object","required":["name","status","started_at"],"properties":{"name":{"type":"string","description":"The instance name: a Spicepod instance (pod) name, or a Cloud Connect runtime id. Pass it as `instanceName` to read its logs."},"status":{"type":"string","enum":["deploying","loading","ready","unhealthy","terminating","failed","failed_to_start","online","offline"],"description":"A Spicepod instance is `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, or `failed_to_start`. A Cloud Connect runtime is `online` or `offline`."},"started_at":{"type":["string","null"],"format":"date-time","description":"When the Spicepod instance started, or when the Cloud Connect runtime enrolled."}}},"ProjectRuntimeError":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["project_not_deployed","project_paused"]}}}}},"paths":{"/v1/apps/{appId}/instances":{"get":{"summary":"List project instances","description":"Legacy alias of `/v1/projects/{projectId}/instances` — projects were previously called apps. Lists the runtime instances of a deployed project, newest first. A managed or BYOC project lists its Spicepod instances. A standalone project lists its Cloud Connect runtime. Read the logs of an instance with `GET /v1/projects/{projectId}/instances/{instanceName}/logs`.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"The project's instances","content":{"application/json":{"schema":{"type":"object","required":["instances"],"properties":{"instances":{"type":"array","items":{"$ref":"#/components/schemas/ProjectInstance"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"The project has no running instances. `code` is `project_not_deployed` when a managed or BYOC project was never deployed, or `project_paused` when the project is paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRuntimeError"}}}},"422":{"description":"The project is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"The project runtime did not answer"}},"tags":["Projects"],"deprecated":true}}}}
```

## Get project instance logs

> Legacy alias of \`/v1/projects/{projectId}/instances/{instanceName}/logs\` — projects were previously called apps. Returns the most recent log lines of one instance of a deployed project, oldest first, without color codes. The org's plan sets the largest \`tail\`. List the instance names with \`GET /v1/projects/{projectId}/instances\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own. A portal session token is bound to none, so it must send this header on every route but `GET /v1/orgs` (400 `org_assertion_required` otherwise).\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}}},"paths":{"/v1/apps/{appId}/instances/{instanceName}/logs":{"get":{"summary":"Get project instance logs","description":"Legacy alias of `/v1/projects/{projectId}/instances/{instanceName}/logs` — projects were previously called apps. Returns the most recent log lines of one instance of a deployed project, oldest first, without color codes. The org's plan sets the largest `tail`. List the instance names with `GET /v1/projects/{projectId}/instances`.","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"instanceName","required":true,"schema":{"type":"string"},"description":"The instance name from `GET /v1/projects/{projectId}/instances`"},{"in":"query","name":"tail","required":false,"schema":{"type":"integer","minimum":1,"default":100},"description":"How many of the most recent lines to return"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"The instance's most recent log lines","content":{"application/json":{"schema":{"type":"object","required":["instance","tail","lines"],"properties":{"instance":{"type":"string","description":"The instance name"},"tail":{"type":"integer","description":"The number of lines asked for"},"lines":{"type":"array","items":{"type":"string"},"description":"Log lines, oldest first"}}}}}},"400":{"description":"Invalid project ID or `tail`"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope (`code` is `insufficient_scope`), or `tail` is larger than the org's plan allows (`code` is `plan_limit`, and `max_tail` is the largest allowed `tail`).","content":{"application/json":{"schema":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"max_tail":{"type":"integer"}}}}}},"404":{"description":"The project was not found, or the instance is not one of the project's instances (`code` is `instance_not_found`)."},"409":{"description":"The logs cannot be read in the project's current state. `code` is `project_not_deployed` when the project was never deployed, `project_paused` when it is paused, or `instance_offline` when the Cloud Connect runtime is offline and does not answer.","content":{"application/json":{"schema":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["project_not_deployed","project_paused","instance_offline"]}}}}}},"422":{"description":"The project is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"The project runtime did not answer"}},"tags":["Projects"],"deprecated":true}}}}
```

## List projects

> The org is \`orgId\` in the path. Returns projects belonging to the authenticated organization. Page with \`limit\` (default 1000, max 1000). Prefer \`after\_id\` for a stable keyset (descending \`id\`). \`offset\` is accepted for compatibility. PostgREST also caps a page at 1000 rows.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"}}}}},"paths":{"/v1/orgs/{orgId}/projects":{"get":{"summary":"List projects","description":"The org is `orgId` in the path. Returns projects belonging to the authenticated organization. Page with `limit` (default 1000, max 1000). Prefer `after_id` for a stable keyset (descending `id`). `offset` is accepted for compatibility. PostgREST also caps a page at 1000 rows.\n","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000},"description":"Maximum number of projects to return"},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of projects to skip when a cursor is not supplied"},{"in":"query","name":"after_id","schema":{"type":"integer","minimum":1},"description":"Return projects with a strictly smaller id than this value."},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"400":{"description":"Invalid limit, offset, or after_id. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."}},"tags":["Projects"]}}}}
```

## Create a project

> The org is \`orgId\` in the path. Creates a new project in the authenticated organization.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/orgs/{orgId}/projects":{"post":{"summary":"Create a project","description":"The org is `orgId` in the path. Creates a new project in the authenticated organization.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":4,"pattern":"^[a-zA-Z0-9-]+$","description":"Project name (letters, numbers, hyphens only)"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Cloud region where a managed project will be deployed (e.g. `us-east-1`). Omit this, `cname`, and `cluster_name` to create an unattached standalone project whose placement is resolved when linked.\n"},"cname":{"type":"string","description":"**Deprecated.** Internal region CNAME (e.g. `us-east-1-prod-aws-data`). Use `region` instead. Accepted for backwards compatibility when `region` is omitted. Omit all region sources to create an unattached standalone project.\n"},"cluster_name":{"type":"string","description":"Name of the dedicated cluster to deploy to, as listed by `GET /v1/clusters`. Provide exactly one of `region` or `cluster_name`. When set, the region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Omit all region sources to create an unattached standalone project.\n"},"description":{"type":"string","description":"Project description"},"visibility":{"type":"string","enum":["public","private"],"default":"private"},"default_alerts":{"type":"boolean","default":true,"description":"Create default monitoring alerts for the new managed project. False skips initial setup. Manual alert creation remains available."},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking."},"replicas":{"type":"integer","minimum":0,"description":"Number of replicas for a managed project. Rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container. Rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project. Rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}}}}}}},"responses":{"201":{"description":"Project created successfully. The response may include a `warning` field if initial configuration could not be applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or invalid region. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"409":{"description":"A project with this name already exists, or the project was deleted while its initial configuration was being written (`code` is `project_deleted`)"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"Project creation failed, including when the name-collision check could not be read","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Generic failure reason; the underlying cause stays in the server logs"}}}}}}},"tags":["Projects"],"parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}]}}}}
```

## Get a project

> The org is \`orgId\` in the path. Returns details for a specific project, including its configuration.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}":{"get":{"summary":"Get a project","description":"The org is `orgId` in the path. Returns details for a specific project, including its configuration.","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"Project details with configuration","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"created_at":{"type":"string","format":"date-time"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":"string"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":"object","additionalProperties":{"type":"string"}},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"config":{"type":"object","properties":{"spicepod":{"type":"object"},"image_tag":{"type":"string","description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime, or null when Spice operates this project's runtime updates."},"version":{"type":"string","description":"Semver range for the runtime version (e.g. 1.x, 2.x)"},"replicas":{"type":"integer"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":["number","null"]}}},"region":{"type":["string","null"],"description":"Server-resolved region; null before a standalone instance is attached."},"storage_size_gb":{"type":["number","null"]},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead."}}}}}}}},"400":{"description":"orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## Update a project

> The org is \`orgId\` in the path. Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}":{"put":{"summary":"Update a project","description":"The org is `orgId` in the path. Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"production_branch":{"type":"string"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"spicepod":{"oneOf":[{"type":"string","description":"YAML string of the spicepod configuration"},{"type":"object","description":"JSON object of the spicepod configuration"}],"description":"The project's Spicepod. A project connected to a GitHub repository takes its Spicepod from the repository's spicepod.yaml, so for such a project only the stored Spicepod, sent back unchanged, is accepted."},"image_tag":{"type":["string","null"],"description":"Runtime image tag for a managed project; rejected for standalone projects. A non-empty value pins the project. On stable, the pin must be a published stable runtime version. On preview, it must be a published preview or stable runtime version. On stable or preview, send null to clear a pin so deploys resolve from the channel catalog (stable - latest GA for the configured version range; preview - latest RC). null is rejected on nightly and internal channels."},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed runtime; rejected for standalone projects."},"version":{"type":"string","description":"Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects."},"replicas":{"type":"integer","minimum":0,"description":"Managed runtime replica count; rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container; rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project; rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number"}}},"region":{"type":"string","description":"Managed project region; standalone placement is resolved during attachment."},"cluster_name":{"type":"string","description":"Reassign the project to a dedicated cluster, as listed by `GET /v1/clusters`. Provide at most one of `region` or `cluster_name`. When set, region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Rejected for standalone projects, whose placement is resolved during attachment.\n"},"storage_size_gb":{"type":"number","description":"Managed runtime storage size; rejected for standalone projects."},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead. Rejected for standalone projects."}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or project ID, including placement or hosted runtime fields that do not apply to standalone projects. When `image_tag` is supplied on stable or preview, a tag that is not a published version for that channel returns `{ error: 'Invalid image_tag for stable channel', code: 'invalid_stable_image_tag' }` (or the preview analog). A `spicepod` that adds a component on a source the Spice runtime no longer loads returns code `retired_component_source`; components the project already stores on such a source may be kept, edited and renamed, and a project backed by a GitHub repository is not checked. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot mutate projects, requested replicas or resources exceed plan limits, dedicated-cluster assignment or special-org private-compute ceilings required, requested resources exceed dedicated-cluster capacity, `image_tag` was supplied outside the Enterprise plan (code: `image_tag_requires_enterprise`), or the requested runtime image is not available for the organization plan (code: `image_tag_not_allowed_for_plan`); omitting `image_tag` leaves the configured image unchanged. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found"},"409":{"description":"The project's lifecycle state changed while the update was in flight — a pause, resume or restore landing concurrently — so the configuration was not saved (code: `project_state_changed`). Re-read the project before retrying: a configuration update clears `paused_at`, so repeating this request against a project that has since been paused also resumes it. Or a `spicepod` that differs from the stored one was sent for a project connected to a GitHub repository (code: `github_connected`); change spicepod.yaml in the repository instead, since nothing in that request is applied."},"500":{"description":"Internal server error"},"502":{"description":"The configuration was stored but could not be published to the spicepod registry (code: `registry_publish_failed`), so the project still deploys its previously published configuration. Retry the request."},"503":{"description":"Runtime version catalog is unavailable"}},"tags":["Projects"]}}}}
```

## Delete a project

> The org is \`orgId\` in the path. Deletes a project and tears down its runtime resources.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}":{"delete":{"summary":"Delete a project","description":"The org is `orgId` in the path. Deletes a project and tears down its runtime resources.","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to delete"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"204":{"description":"Project deleted successfully"},"400":{"description":"Invalid project ID. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot mutate projects. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## List forks of a project

> The org is \`orgId\` in the path. Returns the live projects in the organization that were forked from this project, ordered by name. Requires \`apps:read\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"}}}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/forks":{"get":{"summary":"List forks of a project","description":"The org is `orgId` in the path. Returns the live projects in the organization that were forked from this project, ordered by name. Requires `apps:read`.\n","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the source project"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"Forks of the project","content":{"application/json":{"schema":{"type":"object","required":["forks"],"properties":{"forks":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"400":{"description":"Invalid project ID. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## Fork a project

> The org is \`orgId\` in the path. Creates a new project from this project's configuration and records this project as its source. The fork copies the spicepod the project deploys, its linked connections, project secrets, linked organization secrets, update channel, version range, replica and executor counts, storage size, description, and tags. It does not copy API keys (new ones are generated), deployments, the GitHub repository link, the instance size, or visibility (forks are private). The fork is not deployed: deploy it with POST /v1/projects/{projectId}/deployments. Requires \`apps:write\`. The server copies secret values without returning them, so no secrets scope is needed. A source connected to a GitHub repository is read from that repository, and its spicepod is copied only when it loads no other file from the repository; to deploy a fork from a fork of the repository, fork the project in the Spice portal.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":["string","null"],"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":["string","null"],"description":"Region identifier"},"cluster_name":{"type":["string","null"],"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":["string","null"],"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":["string","null"],"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"forked_from":{"type":["object","null"],"description":"The project this project was forked from, while that project is live in the same organization. Null for a project that was not created as a fork.\n","required":["id","name"],"properties":{"id":{"type":"integer","description":"ID of the source project"},"name":{"type":"string","description":"Name of the source project"}}},"paused_at":{"type":["string","null"],"format":"date-time","description":"Timestamp when the project was paused; null while running"},"warning":{"type":["string","null"],"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":["object","null"],"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":["object","null"],"description":"Spicepod configuration"},"registry":{"type":["string","null"],"description":"Container registry for the runtime image"},"image_tag":{"type":["string","null"],"description":"Runtime image tag"},"update_channel":{"type":["string","null"],"enum":["stable","preview","nightly",null],"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":["object","null"],"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":["object","null"],"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":["string","null"],"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":["string","null"],"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":["number","null"],"description":"Storage claim size in GB"}}}}}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/forks":{"post":{"summary":"Fork a project","description":"The org is `orgId` in the path. Creates a new project from this project's configuration and records this project as its source. The fork copies the spicepod the project deploys, its linked connections, project secrets, linked organization secrets, update channel, version range, replica and executor counts, storage size, description, and tags. It does not copy API keys (new ones are generated), deployments, the GitHub repository link, the instance size, or visibility (forks are private). The fork is not deployed: deploy it with POST /v1/projects/{projectId}/deployments. Requires `apps:write`. The server copies secret values without returning them, so no secrets scope is needed. A source connected to a GitHub repository is read from that repository, and its spicepod is copied only when it loads no other file from the repository; to deploy a fork from a fork of the repository, fork the project in the Spice portal.\n","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to fork"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":4,"maxLength":38,"pattern":"^[a-zA-Z0-9-]+$","description":"Name of the fork. Defaults to the source name with `-fork`, then `-fork-2` and so on, whichever is free.\n"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Region to place the fork in. Omit it and `cluster_name` to place the fork where the source runs.\n"},"cluster_name":{"type":"string","description":"Dedicated cluster to place the fork on, as listed by `GET /v1/clusters`. If `region` is also set, it must match the cluster's region.\n"},"scheduler_state_location":{"type":"string","maxLength":2048,"description":"s3:// URI for the fork's scheduler state. Required when the source spicepod sets `runtime.scheduler.state_location`, and must not overlap it: two schedulers cannot share a location.\n"},"default_alerts":{"type":"boolean","default":true,"description":"Create default monitoring alerts for the new managed fork. False skips initial setup. Manual alert creation remains available."}}}}}},"responses":{"201":{"description":"Project forked. It has not been deployed yet.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProjectWithConfig"},{"type":"object","required":["shared_state"],"properties":{"shared_state":{"type":"array","description":"Settings copied as they are that name external state the source also uses: a replication slot or Kafka consumer group the spicepod names, or a snapshot location. Change them in the fork's spicepod before deploying it if the two projects should not share that state.\n","items":{"type":"object","required":["component","setting","value"],"properties":{"component":{"type":"string","description":"The dataset, or `snapshots`"},"setting":{"type":"string"},"value":{"type":"string"}}}}}}]}}}},"400":{"description":"Invalid project ID or request body, an unknown region or cluster, no `region` or `cluster_name` set for a source whose placement a fork cannot reuse (a BYOC cluster, a self-hosted runtime, or a region or cluster that is no longer available; `code: fork_placement_required`), or a missing or unusable `scheduler_state_location` (`code: scheduler_state_location_required`, `scheduler_state_location_unsupported`, `scheduler_state_location_overlaps_source`, or `scheduler_state_location_not_applicable`). orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"402":{"description":"The source runs models on hosted AI credits, which the organization has used up (`code: ai_credits_exhausted`).\n"},"403":{"description":"Insufficient scope, a viewer role, or the source runs more replicas or executors than the plan or the dedicated cluster allows. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project or organization not found"},"409":{"description":"A project with the requested name already exists, another project creation is in progress, or every default name is taken (`code: fork_name_unavailable`)\n"},"413":{"description":"Request body too large"},"422":{"description":"The source has no spicepod (`code: fork_source_has_no_spicepod`), its spicepod is not valid (`code: fork_source_spicepod_invalid`), or its spicepod loads files from its GitHub repository besides spicepod.yaml (`code: fork_source_loads_repository_files`)\n"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"The fork could not be created. When the failure comes after the project row is created, the fork is deleted again with its spicepod, secrets, and organization secret links. If the project itself cannot be deleted, the response has `code: fork_rollback_failed` and names the partially created project in `project` (`id` and `name`). Delete that project, by `project.id`, before reusing its name.\n"},"502":{"description":"The source is connected to a GitHub repository, and its spicepod could not be read from it (`code: fork_source_repository_unreadable`)\n"}},"tags":["Projects"]}}}}
```

## Pause a project

> The org is \`orgId\` in the path. Pauses the project's running spicepod. The runtime is torn down and the\
> spicepod record is marked as paused. Settings and configuration are\
> preserved. Resume with POST /v1/projects/{projectId}/resume. Requires\
> \`apps:write\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/pause":{"post":{"summary":"Pause a project","description":"The org is `orgId` in the path. Pauses the project's running spicepod. The runtime is torn down and the\nspicepod record is marked as paused. Settings and configuration are\npreserved. Resume with POST /v1/projects/{projectId}/resume. Requires\n`apps:write`.\n","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to pause"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"Project paused","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp when the project was paused"}}}}}},"400":{"description":"Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found"},"409":{"description":"Project is already paused","content":{"application/json":{"schema":{"type":"object","required":["error","paused_at"],"properties":{"error":{"type":"string"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp of the existing pause"}}}}}},"500":{"description":"Internal server error"}},"tags":["Projects"]}}}}
```

## Resume a paused project

> The org is \`orgId\` in the path. Clears the paused state on the project's spicepod and creates a new\
> deployment to bring the runtime back up. A project connected to a\
> GitHub repository deploys spicepod.yaml from its root directory on its\
> production branch, read when the project resumes. Requires\
> \`apps:write\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/resume":{"post":{"summary":"Resume a paused project","description":"The org is `orgId` in the path. Clears the paused state on the project's spicepod and creates a new\ndeployment to bring the runtime back up. A project connected to a\nGitHub repository deploys spicepod.yaml from its root directory on its\nproduction branch, read when the project resumes. Requires\n`apps:write`.\n","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to resume"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"Project resumed, deployment created","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at","deployment_id"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":["string","null"],"format":"date-time","description":"Always null after a successful resume"},"deployment_id":{"type":"integer","description":"ID of the deployment created to bring the runtime back up"}}}}}},"400":{"description":"Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or spicepod configuration is invalid. For a project connected to a GitHub repository, also returned when the root directory has no spicepod.yaml or it is invalid or too large (`code: spicepod_not_found`, `spicepod_invalid` or `spicepod_too_large`). The project stays paused. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, or the org's GitHub App installation cannot read the connected repository (`code: github_unauthorized` or `github_forbidden`). The project stays paused. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found, or the connected GitHub repository or its production branch was not found (`code: github_not_found`). The project stays paused.\n"},"409":{"description":"Project is not paused"},"422":{"description":"No runtime version in the catalog matches this project’s version range (`code: version_no_match`), or the spicepod uses version v1beta1, which the Spice 2 runtime cannot load (`code: spicepod_version_unsupported`).\n"},"429":{"description":"GitHub rate-limited the read of the connected repository (`code: github_rate_limited`). The project stays paused.\n"},"500":{"description":"Internal server error"},"502":{"description":"GitHub did not answer while the connected repository was read (`code: github_unavailable`). The project stays paused.\n"},"503":{"description":"The runtime version catalog is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.\n"}},"tags":["Projects"]}}}}
```

## Connect a project to a GitHub repository

> The org is \`orgId\` in the path. Connects the project to a repository in the organization's GitHub\
> account, through the organization's Spice Cloud GitHub App\
> installation. The next deployment reads spicepod.yaml (or\
> spicepod.yml) from \`root\_directory\` on \`production\_branch\`.\
> Connecting sets the project description to the repository\
> description. Connecting again replaces the repository, root directory\
> and production branch.\
> Requires \`apps:write\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/repository":{"put":{"summary":"Connect a project to a GitHub repository","description":"The org is `orgId` in the path. Connects the project to a repository in the organization's GitHub\naccount, through the organization's Spice Cloud GitHub App\ninstallation. The next deployment reads spicepod.yaml (or\nspicepod.yml) from `root_directory` on `production_branch`.\nConnecting sets the project description to the repository\ndescription. Connecting again replaces the repository, root directory\nand production branch.\nRequires `apps:write`.\n","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to connect"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["repository"],"properties":{"repository":{"type":"string","description":"Repository in the organization's GitHub account, as `name` or `owner/name`. The owner must be the organization's GitHub account."},"root_directory":{"type":["string","null"],"maxLength":512,"description":"Directory inside the repository that contains spicepod.yaml. Omit it, or send null or an empty string, for the repository root."},"production_branch":{"type":"string","maxLength":255,"description":"Branch that deployments read. Defaults to the repository's default branch. A branch that GitHub renamed is stored under its new name."}}}}}},"responses":{"200":{"description":"Project connected","content":{"application/json":{"schema":{"type":"object","required":["project_id","repository","repository_id","root_directory","production_branch","connected_at","has_spicepod"],"properties":{"project_id":{"type":"integer","description":"The project ID"},"repository":{"type":"string","description":"The repository as `owner/name`"},"repository_id":{"type":"integer","description":"The GitHub repository ID"},"root_directory":{"type":["string","null"],"description":"The stored root directory. Null is the repository root."},"production_branch":{"type":"string","description":"The branch that deployments read"},"connected_at":{"type":"string","format":"date-time","description":"When the project was connected"},"has_spicepod":{"type":"boolean","description":"True when spicepod.yaml or spicepod.yml exists in the root directory on the production branch. A deployment fails until it does."}}}}}},"400":{"description":"Invalid project ID or request body, a repository outside the organization's GitHub account (code: `invalid_repository`), or a root directory outside the repository (code: `invalid_root_directory`). orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, or viewer role cannot mutate projects. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found, or the organization's GitHub App installation cannot read the repository (code: `repository_not_found`)"},"409":{"description":"The organization has no usable GitHub App installation (code: `github_not_connected`), or another project already deploys this repository and root directory (code: `root_directory_conflict`)"},"413":{"description":"Request body too large"},"422":{"description":"The repository has no branch named `production_branch` (code: `branch_not_found`), the project has no spicepod configuration (code: `spicepod_missing`), or the organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"GitHub did not answer (code: `github_unavailable`)"}},"tags":["Projects"]}}}}
```

## Get project metrics

> The org is \`orgId\` in the path. Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/metrics":{"get":{"summary":"Get project metrics","description":"The org is `orgId` in the path. Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"window","required":false,"schema":{"type":"string"},"description":"Prometheus-style duration (e.g. `5m`, `1h`, `30s`). When provided, counter metrics are returned as rates over the window instead of raw cumulative values. For example, `rows_ingested` returns average rows per second with a window, or total cumulative rows without one."},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"Resource and ingestion metrics. Missing fields indicate no data is currently available. Individual metric queries may fail independently; partial results are returned when possible.","content":{"application/json":{"schema":{"type":"object","properties":{"ingestion":{"type":"object","description":"Aggregated data ingestion metrics","properties":{"rows_ingested":{"type":"number","description":"Total rows ingested (without window) or rows per second (with window)"},"bytes_ingested":{"type":"number","description":"Total bytes ingested (without window) or bytes per second (with window)"}}},"cluster":{"type":"object","description":"Cluster metrics for the project","properties":{"active_executors_count":{"type":"number","description":"Number of active executors registered with the scheduler"}}},"metrics":{"type":"object","description":"Resource metrics keyed by pod name","additionalProperties":{"type":"object","properties":{"cpu_usage_percent":{"type":"number","description":"CPU usage as a percentage (0-100)"},"memory_usage_bytes":{"type":"number","description":"Memory usage in bytes"},"disk_read_bytes":{"type":"number","description":"Disk bytes read per second (with window) or cumulative (without window)"},"disk_read_operations":{"type":"number","description":"Disk read operations per second (with window) or cumulative count (without window)"},"disk_write_bytes":{"type":"number","description":"Disk bytes written per second (with window) or cumulative (without window)"},"disk_write_operations":{"type":"number","description":"Disk write operations per second (with window) or cumulative count (without window)"}}}}}}}}},"400":{"description":"Invalid project ID. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project or organization not found"},"422":{"description":"Organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"Metrics service unavailable"}},"tags":["Projects"]}}}}
```

## List project instances

> The org is \`orgId\` in the path. Lists the runtime instances of a deployed project, newest first. A managed or BYOC project lists its Spicepod instances. A standalone project lists its Cloud Connect runtime. Read the logs of an instance with \`GET /v1/projects/{projectId}/instances/{instanceName}/logs\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}},"schemas":{"ProjectInstance":{"type":"object","required":["name","status","started_at"],"properties":{"name":{"type":"string","description":"The instance name: a Spicepod instance (pod) name, or a Cloud Connect runtime id. Pass it as `instanceName` to read its logs."},"status":{"type":"string","enum":["deploying","loading","ready","unhealthy","terminating","failed","failed_to_start","online","offline"],"description":"A Spicepod instance is `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, or `failed_to_start`. A Cloud Connect runtime is `online` or `offline`."},"started_at":{"type":["string","null"],"format":"date-time","description":"When the Spicepod instance started, or when the Cloud Connect runtime enrolled."}}},"ProjectRuntimeError":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["project_not_deployed","project_paused"]}}}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/instances":{"get":{"summary":"List project instances","description":"The org is `orgId` in the path. Lists the runtime instances of a deployed project, newest first. A managed or BYOC project lists its Spicepod instances. A standalone project lists its Cloud Connect runtime. Read the logs of an instance with `GET /v1/projects/{projectId}/instances/{instanceName}/logs`.","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"The project's instances","content":{"application/json":{"schema":{"type":"object","required":["instances"],"properties":{"instances":{"type":"array","items":{"$ref":"#/components/schemas/ProjectInstance"}}}}}}},"400":{"description":"Invalid project ID. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope. The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`)."},"404":{"description":"Project not found"},"409":{"description":"The project has no running instances. `code` is `project_not_deployed` when a managed or BYOC project was never deployed, or `project_paused` when the project is paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRuntimeError"}}}},"422":{"description":"The project is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"The project runtime did not answer"}},"tags":["Projects"]}}}}
```

## Get project instance logs

> The org is \`orgId\` in the path. Returns the most recent log lines of one instance of a deployed project, oldest first, without color codes. The org's plan sets the largest \`tail\`. List the instance names with \`GET /v1/projects/{projectId}/instances\`.

```json
{"openapi":"3.1.0","info":{"title":"Spice Cloud Control-Plane API","version":"v1"},"tags":[{"name":"Projects","description":"Manage projects and their configurations"}],"servers":[{"url":"https://api.spice.ai"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"An OAuth 2.0 client-credentials access token, a personal access token, or a portal session token (a Supabase Auth ES256 access token, verified against the issuer's JWKS and refused once its session is signed out).\n"}}},"paths":{"/v1/orgs/{orgId}/projects/{projectId}/instances/{instanceName}/logs":{"get":{"summary":"Get project instance logs","description":"The org is `orgId` in the path. Returns the most recent log lines of one instance of a deployed project, oldest first, without color codes. The org's plan sets the largest `tail`. List the instance names with `GET /v1/projects/{projectId}/instances`.","parameters":[{"in":"path","name":"orgId","required":true,"schema":{"type":"integer","minimum":1},"description":"The org id from GET /v1/orgs"},{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"instanceName","required":true,"schema":{"type":"string"},"description":"The instance name from `GET /v1/projects/{projectId}/instances`"},{"in":"query","name":"tail","required":false,"schema":{"type":"integer","minimum":1,"default":100},"description":"How many of the most recent lines to return"},{"in":"header","name":"X-Org-Name","required":false,"description":"When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org_path_mismatch (400).","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}],"responses":{"200":{"description":"The instance's most recent log lines","content":{"application/json":{"schema":{"type":"object","required":["instance","tail","lines"],"properties":{"instance":{"type":"string","description":"The instance name"},"tail":{"type":"integer","description":"The number of lines asked for"},"lines":{"type":"array","items":{"type":"string"},"description":"Log lines, oldest first"}}}}}},"400":{"description":"Invalid project ID or `tail`. orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`)."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope (`code` is `insufficient_scope`), or `tail` is larger than the org's plan allows (`code` is `plan_limit`, and `max_tail` is the largest allowed `tail`). The caller is not a member of the org (`org_forbidden`). A machine credential named a different org (`org_assertion_mismatch`). The org is deleted (`org_deleted`).","content":{"application/json":{"schema":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"max_tail":{"type":"integer"}}}}}},"404":{"description":"The project was not found, or the instance is not one of the project's instances (`code` is `instance_not_found`)."},"409":{"description":"The logs cannot be read in the project's current state. `code` is `project_not_deployed` when the project was never deployed, `project_paused` when it is paused, or `instance_offline` when the Cloud Connect runtime is offline and does not answer.","content":{"application/json":{"schema":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["project_not_deployed","project_paused","instance_offline"]}}}}}},"422":{"description":"The project is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"The project runtime did not answer"}},"tags":["Projects"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.spice.ai/api/management-api/management/projects.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
