# Projects

Manage projects and their configurations

OpenAPI: [api.spice.ai/openapi.json](https://api.spice.ai/openapi.json)

- [`GET /v1/projects`: List projects](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects#get-v1-projects)
- [`POST /v1/projects`: Create a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects#post-v1-projects)
- [`GET /v1/projects/{projectId}`: Get a project](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid#get-v1-projects-projectid)
- [`PUT /v1/projects/{projectId}`: Update a project](https://spice.ai/docs/cloud/api/management/projects/put-v1-projects-projectid#put-v1-projects-projectid)
- [`DELETE /v1/projects/{projectId}`: Delete a project](https://spice.ai/docs/cloud/api/management/projects/delete-v1-projects-projectid#delete-v1-projects-projectid)
- [`GET /v1/projects/{projectId}/forks`: List forks of a project](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-forks#get-v1-projects-projectid-forks)
- [`POST /v1/projects/{projectId}/forks`: Fork a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-forks#post-v1-projects-projectid-forks)
- [`POST /v1/projects/{projectId}/pause`: Pause a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-pause#post-v1-projects-projectid-pause)
- [`POST /v1/projects/{projectId}/resume`: Resume a paused project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-resume#post-v1-projects-projectid-resume)
- [`PUT /v1/projects/{projectId}/repository`: Connect a project to a GitHub repository](https://spice.ai/docs/cloud/api/management/projects/put-v1-projects-projectid-repository#put-v1-projects-projectid-repository)
- [`GET /v1/projects/{projectId}/metrics`: Get project metrics](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-metrics#get-v1-projects-projectid-metrics)
- [`GET /v1/projects/{projectId}/instances`: List project instances](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-instances#get-v1-projects-projectid-instances)
- [`GET /v1/projects/{projectId}/instances/{instanceName}/logs`: Get project instance logs](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-instances-instancename-logs#get-v1-projects-projectid-instances-instancename-logs)
- [`GET /v1/orgs/{orgId}/projects`: List projects](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects#get-v1-orgs-orgid-projects)
- [`POST /v1/orgs/{orgId}/projects`: Create a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects#post-v1-orgs-orgid-projects)
- [`GET /v1/orgs/{orgId}/projects/{projectId}`: Get a project](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid#get-v1-orgs-orgid-projects-projectid)
- [`PUT /v1/orgs/{orgId}/projects/{projectId}`: Update a project](https://spice.ai/docs/cloud/api/management/projects/put-v1-projects-projectid#put-v1-orgs-orgid-projects-projectid)
- [`DELETE /v1/orgs/{orgId}/projects/{projectId}`: Delete a project](https://spice.ai/docs/cloud/api/management/projects/delete-v1-projects-projectid#delete-v1-orgs-orgid-projects-projectid)
- [`GET /v1/orgs/{orgId}/projects/{projectId}/forks`: List forks of a project](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-forks#get-v1-orgs-orgid-projects-projectid-forks)
- [`POST /v1/orgs/{orgId}/projects/{projectId}/forks`: Fork a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-forks#post-v1-orgs-orgid-projects-projectid-forks)
- [`POST /v1/orgs/{orgId}/projects/{projectId}/pause`: Pause a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-pause#post-v1-orgs-orgid-projects-projectid-pause)
- [`POST /v1/orgs/{orgId}/projects/{projectId}/resume`: Resume a paused project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-resume#post-v1-orgs-orgid-projects-projectid-resume)
- [`PUT /v1/orgs/{orgId}/projects/{projectId}/repository`: Connect a project to a GitHub repository](https://spice.ai/docs/cloud/api/management/projects/put-v1-projects-projectid-repository#put-v1-orgs-orgid-projects-projectid-repository)
- [`GET /v1/orgs/{orgId}/projects/{projectId}/metrics`: Get project metrics](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-metrics#get-v1-orgs-orgid-projects-projectid-metrics)
- [`GET /v1/orgs/{orgId}/projects/{projectId}/instances`: List project instances](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-instances#get-v1-orgs-orgid-projects-projectid-instances)
- [`GET /v1/orgs/{orgId}/projects/{projectId}/instances/{instanceName}/logs`: Get project instance logs](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-instances-instancename-logs#get-v1-orgs-orgid-projects-projectid-instances-instancename-logs)
- [`GET /v1/apps`: List projects](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects#get-v1-apps) (deprecated)
- [`POST /v1/apps`: Create a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects#post-v1-apps) (deprecated)
- [`GET /v1/apps/{appId}`: Get a project](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid#get-v1-apps-appid) (deprecated)
- [`PUT /v1/apps/{appId}`: Update a project](https://spice.ai/docs/cloud/api/management/projects/put-v1-projects-projectid#put-v1-apps-appid) (deprecated)
- [`DELETE /v1/apps/{appId}`: Delete a project](https://spice.ai/docs/cloud/api/management/projects/delete-v1-projects-projectid#delete-v1-apps-appid) (deprecated)
- [`GET /v1/apps/{appId}/forks`: List forks of a project](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-forks#get-v1-apps-appid-forks) (deprecated)
- [`POST /v1/apps/{appId}/forks`: Fork a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-forks#post-v1-apps-appid-forks) (deprecated)
- [`POST /v1/apps/{appId}/pause`: Pause a project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-pause#post-v1-apps-appid-pause) (deprecated)
- [`POST /v1/apps/{appId}/resume`: Resume a paused project](https://spice.ai/docs/cloud/api/management/projects/post-v1-projects-projectid-resume#post-v1-apps-appid-resume) (deprecated)
- [`PUT /v1/apps/{appId}/repository`: Connect a project to a GitHub repository](https://spice.ai/docs/cloud/api/management/projects/put-v1-projects-projectid-repository#put-v1-apps-appid-repository) (deprecated)
- [`GET /v1/apps/{appId}/metrics`: Get project metrics](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-metrics#get-v1-apps-appid-metrics) (deprecated)
- [`GET /v1/apps/{appId}/instances`: List project instances](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-instances#get-v1-apps-appid-instances) (deprecated)
- [`GET /v1/apps/{appId}/instances/{instanceName}/logs`: Get project instance logs](https://spice.ai/docs/cloud/api/management/projects/get-v1-projects-projectid-instances-instancename-logs#get-v1-apps-appid-instances-instancename-logs) (deprecated)

## List projects

`GET /v1/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.

### Query parameters

- `limit` (integer, optional): Maximum number of projects to return. Default: `1000`. Minimum: `1`. Maximum: `1000`.
- `offset` (integer, optional): Number of projects to skip when a cursor is not supplied. Default: `0`. Minimum: `0`.
- `after_id` (integer, optional): Return projects with a strictly smaller id than this value. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/projects" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/projects HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

List of projects

Content type: `application/json`

- `projects` (array of object)
  - `id` (integer): Unique identifier for the project
  - `name` (string): Name of the project
  - `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
  - `description` (string | null): Description of the project
  - `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
  - `created_at` (string (date-time)): Timestamp when the project was created
  - `cname` (string | null): Region identifier
  - `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
  - `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
  - `tags` (map of string | null): Key-value tags for the project

**400 Bad Request**

Invalid limit, offset, or after\_id

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

## Create a project

`POST /v1/projects`

Creates a new project in the authenticated organization.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `name` (string, required): Project name (letters, numbers, hyphens only). Minimum length: `4`. Pattern: `^[a-zA-Z0-9-]+$`.
- `region` (string): 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. Allowed values: `us-east-1`, `us-west-2`.
- `cname` (string): **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.
- `cluster_name` (string): 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.
- `description` (string): Project description
- `visibility` (string): Allowed values: `public`, `private`. Default: `private`.
- `default_alerts` (boolean): Create default monitoring alerts for the new managed project. False skips initial setup. Manual alert creation remains available. Default: `true`.
- `tags` (map of string): Key-value tags for the project
- `update_channel` (string): Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking. Allowed values: `stable`, `preview`, `nightly`.
- `replicas` (integer): Number of replicas for a managed project. Rejected for standalone projects. Minimum: `0`.
- `resources` (object): Resource requests and limits for a managed project container. Rejected for standalone projects.
  - `limits` (object)
    - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
    - `memory` (string): Memory limit in Gi (for example, 16Gi)
    - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
  - `requests` (object)
    - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
- `executor` (object): Executor container configuration for a managed project. Rejected for standalone projects.
  - `replicas` (integer): Number of executor replicas. Minimum: `0`.
  - `resources` (object): Resource requests and limits for the executor container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/projects" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string"
  }'
```

```http
POST /v1/projects HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 22

{
  "name": "string"
}
```

### Responses

**201 Created**

Project created successfully. The response may include a `warning` field if initial configuration could not be applied.

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB

**400 Bad Request**

Invalid request body or invalid region

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity

**404 Not Found**

Organization not found, including an organization deleted while the project was being created

**409 Conflict**

A project with this name already exists, or the project was deleted while its initial configuration was being written (`code` is `project_deleted`)

**429 Too Many Requests**

Too many project creation requests (rate limited)

**500 Internal Server Error**

Project creation failed, including when the name-collision check could not be read

Content type: `application/json`

- `error` (string, required): Generic failure reason; the underlying cause stays in the server logs. Example: `Failed to create project`.

## Get a project

`GET /v1/projects/{projectId}`

Returns details for a specific project, including its configuration.

### Path parameters

- `projectId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/projects/<projectId>" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/projects/<projectId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project details with configuration

Content type: `application/json`

- `id` (integer)
- `name` (string)
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string)
- `visibility` (string): Allowed values: `public`, `private`.
- `created_at` (string (date-time))
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string)
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string)
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `config` (object)
  - `spicepod` (object)
  - `image_tag` (string): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime, or null when Spice operates this project's runtime updates. Allowed values: `stable`, `preview`, `nightly`.
  - `version` (string): Semver range for the runtime version (e.g. 1.x, 2.x)
  - `replicas` (integer)
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
    - `storage_size_gb` (number | null)
  - `region` (string | null): Server-resolved region; null before a standalone instance is attached.
  - `storage_size_gb` (number | null)
  - `storage_claim_size_gb` (number, deprecated): Deprecated. Use storage\_size\_gb instead.

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## Update a project

`PUT /v1/projects/{projectId}`

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

### Path parameters

- `projectId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `description` (string)
- `visibility` (string): Allowed values: `public`, `private`.
- `production_branch` (string)
- `tags` (map of string): Key-value tags for the project. The map replaces the stored tags. System tags the project already has stay on the project when the map omits them.
- `spicepod` (string | object): 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` (string | null): 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` (string): Update channel for a managed runtime; rejected for standalone projects. Allowed values: `stable`, `preview`, `nightly`.
- `version` (string): Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects.
- `replicas` (integer): Managed runtime replica count; rejected for standalone projects. Minimum: `0`.
- `resources` (object): Resource requests and limits for a managed project container; rejected for standalone projects.
  - `limits` (object)
    - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
    - `memory` (string): Memory limit in Gi (for example, 16Gi)
    - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
  - `requests` (object)
    - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
- `executor` (object): Executor container configuration for a managed project; rejected for standalone projects.
  - `replicas` (integer): Number of executor replicas. Minimum: `0`.
  - `resources` (object): Resource requests and limits for the executor container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `storage_size_gb` (number)
- `region` (string): Managed project region; standalone placement is resolved during attachment.
- `cluster_name` (string): 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.
- `storage_size_gb` (number): Managed runtime storage size; rejected for standalone projects.
- `storage_claim_size_gb` (number, deprecated): Deprecated. Use storage\_size\_gb instead. Rejected for standalone projects.

### Example request

```bash
curl -X PUT "https://api.spice.ai/v1/projects/<projectId>" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "string",
    "visibility": "public",
    "production_branch": "string",
    "tags": {},
    "spicepod": "string",
    "image_tag": "string",
    "update_channel": "stable",
    "version": "string",
    "replicas": 0,
    "resources": {},
    "executor": {},
    "region": "string",
    "cluster_name": "string",
    "storage_size_gb": 0,
    "storage_claim_size_gb": 0
  }'
```

```http
PUT /v1/projects/<projectId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 362

{
  "description": "string",
  "visibility": "public",
  "production_branch": "string",
  "tags": {},
  "spicepod": "string",
  "image_tag": "string",
  "update_channel": "stable",
  "version": "string",
  "replicas": 0,
  "resources": {},
  "executor": {},
  "region": "string",
  "cluster_name": "string",
  "storage_size_gb": 0,
  "storage_claim_size_gb": 0
}
```

### Responses

**200 OK**

Project updated successfully

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**409 Conflict**

The project's lifecycle state changed while the update was in flight (a pause, resume, or restore landed at the same time), 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 Internal Server Error**

Internal server error

**502 Bad Gateway**

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 Service Unavailable**

Runtime version catalog is unavailable

## Delete a project

`DELETE /v1/projects/{projectId}`

Deletes a project and tears down its runtime resources.

### Path parameters

- `projectId` (integer, required): The ID of the project to delete

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X DELETE "https://api.spice.ai/v1/projects/<projectId>" \
  -H "Authorization: Bearer <token>"
```

```http
DELETE /v1/projects/<projectId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**204 No Content**

Project deleted successfully

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope or viewer role cannot mutate projects

**404 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## List forks of a project

`GET /v1/projects/{projectId}/forks`

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

### Path parameters

- `projectId` (integer, required): The ID of the source project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/projects/<projectId>/forks" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/projects/<projectId>/forks HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Forks of the project

Content type: `application/json`

- `forks` (array of object, required)
  - `id` (integer): Unique identifier for the project
  - `name` (string): Name of the project
  - `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
  - `description` (string | null): Description of the project
  - `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
  - `created_at` (string (date-time)): Timestamp when the project was created
  - `cname` (string | null): Region identifier
  - `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
  - `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
  - `tags` (map of string | null): Key-value tags for the project

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## Fork a project

`POST /v1/projects/{projectId}/forks`

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.

### Path parameters

- `projectId` (integer, required): The ID of the project to fork

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Optional.

Content type: `application/json`

- `name` (string): Name of the fork. Defaults to the source name with `-fork`, then `-fork-2` and so on, whichever is free. Minimum length: `4`. Maximum length: `38`. Pattern: `^[a-zA-Z0-9-]+$`.
- `region` (string): Region to place the fork in. Omit it and `cluster_name` to place the fork where the source runs. Allowed values: `us-east-1`, `us-west-2`.
- `cluster_name` (string): 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.
- `scheduler_state_location` (string): 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. Maximum length: `2048`.
- `default_alerts` (boolean): Create default monitoring alerts for the new managed fork. False skips initial setup. Manual alert creation remains available. Default: `true`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/projects/<projectId>/forks" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string",
    "region": "us-east-1",
    "cluster_name": "string",
    "scheduler_state_location": "string",
    "default_alerts": true
  }'
```

```http
POST /v1/projects/<projectId>/forks HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 141

{
  "name": "string",
  "region": "us-east-1",
  "cluster_name": "string",
  "scheduler_state_location": "string",
  "default_alerts": true
}
```

### Responses

**201 Created**

Project forked. It has not been deployed yet.

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB
- `shared_state` (array of object, required): 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.
  - `component` (string, required): The dataset, or `snapshots`
  - `setting` (string, required): Example: `pg_replication_slot`.
  - `value` (string, required)

**400 Bad Request**

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`).

**401 Unauthorized**

Unauthorized

**402 Payment Required**

The source runs models on hosted AI credits, which the organization has used up (`code: ai_credits_exhausted`).

**403 Forbidden**

Insufficient scope, a viewer role, or the source runs more replicas or executors than the plan or the dedicated cluster allows

**404 Not Found**

Project or organization not found

**409 Conflict**

A project with the requested name already exists, another project creation is in progress, or every default name is taken (`code: fork_name_unavailable`)

**413 Content Too Large**

Request body too large

**422 Unprocessable Content**

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`)

**429 Too Many Requests**

Too many project creation requests (rate limited)

**500 Internal Server Error**

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.

**502 Bad Gateway**

The source is connected to a GitHub repository, and its spicepod could not be read from it (`code: fork_source_repository_unreadable`)

## Pause a project

`POST /v1/projects/{projectId}/pause`

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`.

### Path parameters

- `projectId` (integer, required): The ID of the project to pause

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/projects/<projectId>/pause" \
  -H "Authorization: Bearer <token>"
```

```http
POST /v1/projects/<projectId>/pause HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project paused

Content type: `application/json`

- `id` (integer, required): The project ID
- `name` (string, required): The project name
- `paused_at` (string (date-time), required): Timestamp when the project was paused

**400 Bad Request**

Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**409 Conflict**

Project is already paused

Content type: `application/json`

- `error` (string, required)
- `paused_at` (string (date-time), required): Timestamp of the existing pause

**500 Internal Server Error**

Internal server error

## Resume a paused project

`POST /v1/projects/{projectId}/resume`

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`.

### Path parameters

- `projectId` (integer, required): The ID of the project to resume

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/projects/<projectId>/resume" \
  -H "Authorization: Bearer <token>"
```

```http
POST /v1/projects/<projectId>/resume HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project resumed, deployment created

Content type: `application/json`

- `id` (integer, required): The project ID
- `name` (string, required): The project name
- `paused_at` (string (date-time) | null, required): Always null after a successful resume
- `deployment_id` (integer, required): ID of the deployment created to bring the runtime back up

**400 Bad Request**

Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or the spicepod `name`, `kind` or `version` is missing or not valid (the body carries an `issues` array of `{path, message}`). 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.

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope, or the org's GitHub App installation cannot read the connected repository (`code: github_unauthorized` or `github_forbidden`). The project stays paused.

**404 Not Found**

Project not found, or the connected GitHub repository or its production branch was not found (`code: github_not_found`). The project stays paused.

**409 Conflict**

Project is not paused, or its spicepod was deleted after the request read it. Also returned when another deployment of the project is queued (`code: deployment_in_progress`); the project then stays paused. Also returned when the project's repository, root directory, or production branch changes while the spicepod is read from the repository; the project then stays paused. Resume again.

**422 Unprocessable Content**

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`). Also returned when the spicepod uses a connection credential that holds a secret reference deploy cannot resolve (`code: connection_credentials_unresolved`, with an `unresolved_credentials` array of `{connection, field}`). The project stays paused.

**429 Too Many Requests**

GitHub rate-limited the read of the connected repository (`code: github_rate_limited`). The project stays paused.

**500 Internal Server Error**

Internal server error

**502 Bad Gateway**

GitHub did not answer while the connected repository was read (`code: github_unavailable`). The project stays paused.

**503 Service Unavailable**

The runtime version catalog is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.

## Connect a project to a GitHub repository

`PUT /v1/projects/{projectId}/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`.

### Path parameters

- `projectId` (integer, required): The ID of the project to connect

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `repository` (string, required): Repository in the organization's GitHub account, as `name` or `owner/name`. The owner must be the organization's GitHub account. Example: `cookbook`.
- `root_directory` (string | null): Directory inside the repository that contains spicepod.yaml. Omit it, or send null or an empty string, for the repository root. Maximum length: `512`. Example: `cayenne`.
- `production_branch` (string): Branch that deployments read. Defaults to the repository's default branch. A branch that GitHub renamed is stored under its new name. Maximum length: `255`. Example: `trunk`.

### Example request

```bash
curl -X PUT "https://api.spice.ai/v1/projects/<projectId>/repository" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "repository": "cookbook"
  }'
```

```http
PUT /v1/projects/<projectId>/repository HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 30

{
  "repository": "cookbook"
}
```

### Responses

**200 OK**

Project connected

Content type: `application/json`

- `project_id` (integer, required): The project ID
- `repository` (string, required): The repository as `owner/name`
- `repository_id` (integer, required): The GitHub repository ID
- `root_directory` (string | null, required): The stored root directory. Null is the repository root.
- `production_branch` (string, required): The branch that deployments read
- `connected_at` (string (date-time), required): When the project was connected
- `has_spicepod` (boolean, required): True when spicepod.yaml or spicepod.yml exists in the root directory on the production branch. A deployment fails until it does.

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope, or viewer role cannot mutate projects

**404 Not Found**

Project not found, or the organization's GitHub App installation cannot read the repository (code: `repository_not_found`)

**409 Conflict**

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 Content Too Large**

Request body too large

**422 Unprocessable Content**

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 Internal Server Error**

Internal server error

**503 Service Unavailable**

GitHub did not answer (code: `github_unavailable`)

## Get project metrics

`GET /v1/projects/{projectId}/metrics`

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

### Path parameters

- `projectId` (integer, required): The ID of the project

### Query parameters

- `window` (string, optional): 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.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/projects/<projectId>/metrics" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/projects/<projectId>/metrics HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

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 type: `application/json`

- `ingestion` (object): Aggregated data ingestion metrics
  - `rows_ingested` (number): Total rows ingested (without window) or rows per second (with window)
  - `bytes_ingested` (number): Total bytes ingested (without window) or bytes per second (with window)
- `cluster` (object): Cluster metrics for the project
  - `active_executors_count` (number): Number of active executors registered with the scheduler
- `metrics` (map of object): Resource metrics keyed by pod name
  - `cpu_usage_percent` (number): CPU usage as a percentage (0-100)
  - `memory_usage_bytes` (number): Memory usage in bytes
  - `disk_read_bytes` (number): Disk bytes read per second (with window) or cumulative (without window)
  - `disk_read_operations` (number): Disk read operations per second (with window) or cumulative count (without window)
  - `disk_write_bytes` (number): Disk bytes written per second (with window) or cumulative (without window)
  - `disk_write_operations` (number): Disk write operations per second (with window) or cumulative count (without window)

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project or organization not found

**422 Unprocessable Content**

Organization is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

Metrics service unavailable

## List project instances

`GET /v1/projects/{projectId}/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`.

### Path parameters

- `projectId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/projects/<projectId>/instances" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/projects/<projectId>/instances HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

The project's instances

Content type: `application/json`

- `instances` (array of object, required)
  - `name` (string, required): The instance name: a Spicepod instance (pod) name, or a Cloud Connect runtime id. Pass it as `instanceName` to read its logs.
  - `status` (string, required): A Spicepod instance is `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, or `failed_to_start`. A Cloud Connect runtime is `online` or `offline`. Allowed values: `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, `failed_to_start`, `online`, `offline`.
  - `started_at` (string (date-time) | null, required): When the Spicepod instance started, or when the Cloud Connect runtime enrolled.

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**409 Conflict**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required): Allowed values: `project_not_deployed`, `project_paused`.

**422 Unprocessable Content**

The project is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

The project runtime did not answer

## Get project instance logs

`GET /v1/projects/{projectId}/instances/{instanceName}/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`.

### Path parameters

- `projectId` (integer, required): The ID of the project
- `instanceName` (string, required): The instance name from `GET /v1/projects/{projectId}/instances`

### Query parameters

- `tail` (integer, optional): How many of the most recent lines to return. Default: `100`. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/projects/<projectId>/instances/<instanceName>/logs" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/projects/<projectId>/instances/<instanceName>/logs HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

The instance's most recent log lines

Content type: `application/json`

- `instance` (string, required): The instance name
- `tail` (integer, required): The number of lines asked for
- `lines` (array of string, required): Log lines, oldest first

**400 Bad Request**

Invalid project ID or `tail`

**401 Unauthorized**

Unauthorized

**403 Forbidden**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required)
- `max_tail` (integer)

**404 Not Found**

The project was not found, or the instance is not one of the project's instances (`code` is `instance_not_found`).

**409 Conflict**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required): Allowed values: `project_not_deployed`, `project_paused`, `instance_offline`.

**422 Unprocessable Content**

The project is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

The project runtime did not answer

## List projects

`GET /v1/orgs/{orgId}/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.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.

### Query parameters

- `limit` (integer, optional): Maximum number of projects to return. Default: `1000`. Minimum: `1`. Maximum: `1000`.
- `offset` (integer, optional): Number of projects to skip when a cursor is not supplied. Default: `0`. Minimum: `0`.
- `after_id` (integer, optional): Return projects with a strictly smaller id than this value. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/orgs/<orgId>/projects" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/orgs/<orgId>/projects HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

List of projects

Content type: `application/json`

- `projects` (array of object)
  - `id` (integer): Unique identifier for the project
  - `name` (string): Name of the project
  - `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
  - `description` (string | null): Description of the project
  - `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
  - `created_at` (string (date-time)): Timestamp when the project was created
  - `cname` (string | null): Region identifier
  - `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
  - `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
  - `tags` (map of string | null): Key-value tags for the project

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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`).

## Create a project

`POST /v1/orgs/{orgId}/projects`

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

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `name` (string, required): Project name (letters, numbers, hyphens only). Minimum length: `4`. Pattern: `^[a-zA-Z0-9-]+$`.
- `region` (string): 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. Allowed values: `us-east-1`, `us-west-2`.
- `cname` (string): **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.
- `cluster_name` (string): 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.
- `description` (string): Project description
- `visibility` (string): Allowed values: `public`, `private`. Default: `private`.
- `default_alerts` (boolean): Create default monitoring alerts for the new managed project. False skips initial setup. Manual alert creation remains available. Default: `true`.
- `tags` (map of string): Key-value tags for the project
- `update_channel` (string): Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking. Allowed values: `stable`, `preview`, `nightly`.
- `replicas` (integer): Number of replicas for a managed project. Rejected for standalone projects. Minimum: `0`.
- `resources` (object): Resource requests and limits for a managed project container. Rejected for standalone projects.
  - `limits` (object)
    - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
    - `memory` (string): Memory limit in Gi (for example, 16Gi)
    - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
  - `requests` (object)
    - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
- `executor` (object): Executor container configuration for a managed project. Rejected for standalone projects.
  - `replicas` (integer): Number of executor replicas. Minimum: `0`.
  - `resources` (object): Resource requests and limits for the executor container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/orgs/<orgId>/projects" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string"
  }'
```

```http
POST /v1/orgs/<orgId>/projects HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 22

{
  "name": "string"
}
```

### Responses

**201 Created**

Project created successfully. The response may include a `warning` field if initial configuration could not be applied.

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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`).

**404 Not Found**

Organization not found, including an organization deleted while the project was being created

**409 Conflict**

A project with this name already exists, or the project was deleted while its initial configuration was being written (`code` is `project_deleted`)

**429 Too Many Requests**

Too many project creation requests (rate limited)

**500 Internal Server Error**

Project creation failed, including when the name-collision check could not be read

Content type: `application/json`

- `error` (string, required): Generic failure reason; the underlying cause stays in the server logs. Example: `Failed to create project`.

## Get a project

`GET /v1/orgs/{orgId}/projects/{projectId}`

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

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/orgs/<orgId>/projects/<projectId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project details with configuration

Content type: `application/json`

- `id` (integer)
- `name` (string)
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string)
- `visibility` (string): Allowed values: `public`, `private`.
- `created_at` (string (date-time))
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string)
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string)
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `config` (object)
  - `spicepod` (object)
  - `image_tag` (string): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime, or null when Spice operates this project's runtime updates. Allowed values: `stable`, `preview`, `nightly`.
  - `version` (string): Semver range for the runtime version (e.g. 1.x, 2.x)
  - `replicas` (integer)
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
    - `storage_size_gb` (number | null)
  - `region` (string | null): Server-resolved region; null before a standalone instance is attached.
  - `storage_size_gb` (number | null)
  - `storage_claim_size_gb` (number, deprecated): Deprecated. Use storage\_size\_gb instead.

**400 Bad Request**

orgId is not a valid org id (`invalid_org_id`), or X-Org-Name does not name that org (`org_path_mismatch`).

**401 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## Update a project

`PUT /v1/orgs/{orgId}/projects/{projectId}`

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

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `description` (string)
- `visibility` (string): Allowed values: `public`, `private`.
- `production_branch` (string)
- `tags` (map of string): Key-value tags for the project. The map replaces the stored tags. System tags the project already has stay on the project when the map omits them.
- `spicepod` (string | object): 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` (string | null): 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` (string): Update channel for a managed runtime; rejected for standalone projects. Allowed values: `stable`, `preview`, `nightly`.
- `version` (string): Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects.
- `replicas` (integer): Managed runtime replica count; rejected for standalone projects. Minimum: `0`.
- `resources` (object): Resource requests and limits for a managed project container; rejected for standalone projects.
  - `limits` (object)
    - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
    - `memory` (string): Memory limit in Gi (for example, 16Gi)
    - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
  - `requests` (object)
    - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
- `executor` (object): Executor container configuration for a managed project; rejected for standalone projects.
  - `replicas` (integer): Number of executor replicas. Minimum: `0`.
  - `resources` (object): Resource requests and limits for the executor container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `storage_size_gb` (number)
- `region` (string): Managed project region; standalone placement is resolved during attachment.
- `cluster_name` (string): 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.
- `storage_size_gb` (number): Managed runtime storage size; rejected for standalone projects.
- `storage_claim_size_gb` (number, deprecated): Deprecated. Use storage\_size\_gb instead. Rejected for standalone projects.

### Example request

```bash
curl -X PUT "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "string",
    "visibility": "public",
    "production_branch": "string",
    "tags": {},
    "spicepod": "string",
    "image_tag": "string",
    "update_channel": "stable",
    "version": "string",
    "replicas": 0,
    "resources": {},
    "executor": {},
    "region": "string",
    "cluster_name": "string",
    "storage_size_gb": 0,
    "storage_claim_size_gb": 0
  }'
```

```http
PUT /v1/orgs/<orgId>/projects/<projectId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 362

{
  "description": "string",
  "visibility": "public",
  "production_branch": "string",
  "tags": {},
  "spicepod": "string",
  "image_tag": "string",
  "update_channel": "stable",
  "version": "string",
  "replicas": 0,
  "resources": {},
  "executor": {},
  "region": "string",
  "cluster_name": "string",
  "storage_size_gb": 0,
  "storage_claim_size_gb": 0
}
```

### Responses

**200 OK**

Project updated successfully

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**409 Conflict**

The project's lifecycle state changed while the update was in flight (a pause, resume, or restore landed at the same time), 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 Internal Server Error**

Internal server error

**502 Bad Gateway**

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 Service Unavailable**

Runtime version catalog is unavailable

## Delete a project

`DELETE /v1/orgs/{orgId}/projects/{projectId}`

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

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project to delete

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X DELETE "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>" \
  -H "Authorization: Bearer <token>"
```

```http
DELETE /v1/orgs/<orgId>/projects/<projectId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**204 No Content**

Project deleted successfully

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## List forks of a project

`GET /v1/orgs/{orgId}/projects/{projectId}/forks`

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`.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the source project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/forks" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/orgs/<orgId>/projects/<projectId>/forks HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Forks of the project

Content type: `application/json`

- `forks` (array of object, required)
  - `id` (integer): Unique identifier for the project
  - `name` (string): Name of the project
  - `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
  - `description` (string | null): Description of the project
  - `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
  - `created_at` (string (date-time)): Timestamp when the project was created
  - `cname` (string | null): Region identifier
  - `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
  - `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
  - `tags` (map of string | null): Key-value tags for the project

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## Fork a project

`POST /v1/orgs/{orgId}/projects/{projectId}/forks`

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.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project to fork

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Optional.

Content type: `application/json`

- `name` (string): Name of the fork. Defaults to the source name with `-fork`, then `-fork-2` and so on, whichever is free. Minimum length: `4`. Maximum length: `38`. Pattern: `^[a-zA-Z0-9-]+$`.
- `region` (string): Region to place the fork in. Omit it and `cluster_name` to place the fork where the source runs. Allowed values: `us-east-1`, `us-west-2`.
- `cluster_name` (string): 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.
- `scheduler_state_location` (string): 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. Maximum length: `2048`.
- `default_alerts` (boolean): Create default monitoring alerts for the new managed fork. False skips initial setup. Manual alert creation remains available. Default: `true`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/forks" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string",
    "region": "us-east-1",
    "cluster_name": "string",
    "scheduler_state_location": "string",
    "default_alerts": true
  }'
```

```http
POST /v1/orgs/<orgId>/projects/<projectId>/forks HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 141

{
  "name": "string",
  "region": "us-east-1",
  "cluster_name": "string",
  "scheduler_state_location": "string",
  "default_alerts": true
}
```

### Responses

**201 Created**

Project forked. It has not been deployed yet.

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB
- `shared_state` (array of object, required): 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.
  - `component` (string, required): The dataset, or `snapshots`
  - `setting` (string, required): Example: `pg_replication_slot`.
  - `value` (string, required)

**400 Bad Request**

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 Unauthorized**

Unauthorized

**402 Payment Required**

The source runs models on hosted AI credits, which the organization has used up (`code: ai_credits_exhausted`).

**403 Forbidden**

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 Not Found**

Project or organization not found

**409 Conflict**

A project with the requested name already exists, another project creation is in progress, or every default name is taken (`code: fork_name_unavailable`)

**413 Content Too Large**

Request body too large

**422 Unprocessable Content**

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`)

**429 Too Many Requests**

Too many project creation requests (rate limited)

**500 Internal Server Error**

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.

**502 Bad Gateway**

The source is connected to a GitHub repository, and its spicepod could not be read from it (`code: fork_source_repository_unreadable`)

## Pause a project

`POST /v1/orgs/{orgId}/projects/{projectId}/pause`

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`.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project to pause

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/pause" \
  -H "Authorization: Bearer <token>"
```

```http
POST /v1/orgs/<orgId>/projects/<projectId>/pause HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project paused

Content type: `application/json`

- `id` (integer, required): The project ID
- `name` (string, required): The project name
- `paused_at` (string (date-time), required): Timestamp when the project was paused

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**409 Conflict**

Project is already paused

Content type: `application/json`

- `error` (string, required)
- `paused_at` (string (date-time), required): Timestamp of the existing pause

**500 Internal Server Error**

Internal server error

## Resume a paused project

`POST /v1/orgs/{orgId}/projects/{projectId}/resume`

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`.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project to resume

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/resume" \
  -H "Authorization: Bearer <token>"
```

```http
POST /v1/orgs/<orgId>/projects/<projectId>/resume HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project resumed, deployment created

Content type: `application/json`

- `id` (integer, required): The project ID
- `name` (string, required): The project name
- `paused_at` (string (date-time) | null, required): Always null after a successful resume
- `deployment_id` (integer, required): ID of the deployment created to bring the runtime back up

**400 Bad Request**

Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or the spicepod `name`, `kind` or `version` is missing or not valid (the body carries an `issues` array of `{path, message}`). 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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found, or the connected GitHub repository or its production branch was not found (`code: github_not_found`). The project stays paused.

**409 Conflict**

Project is not paused, or its spicepod was deleted after the request read it. Also returned when another deployment of the project is queued (`code: deployment_in_progress`); the project then stays paused. Also returned when the project's repository, root directory, or production branch changes while the spicepod is read from the repository; the project then stays paused. Resume again.

**422 Unprocessable Content**

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`). Also returned when the spicepod uses a connection credential that holds a secret reference deploy cannot resolve (`code: connection_credentials_unresolved`, with an `unresolved_credentials` array of `{connection, field}`). The project stays paused.

**429 Too Many Requests**

GitHub rate-limited the read of the connected repository (`code: github_rate_limited`). The project stays paused.

**500 Internal Server Error**

Internal server error

**502 Bad Gateway**

GitHub did not answer while the connected repository was read (`code: github_unavailable`). The project stays paused.

**503 Service Unavailable**

The runtime version catalog is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.

## Connect a project to a GitHub repository

`PUT /v1/orgs/{orgId}/projects/{projectId}/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`.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project to connect

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `repository` (string, required): Repository in the organization's GitHub account, as `name` or `owner/name`. The owner must be the organization's GitHub account. Example: `cookbook`.
- `root_directory` (string | null): Directory inside the repository that contains spicepod.yaml. Omit it, or send null or an empty string, for the repository root. Maximum length: `512`. Example: `cayenne`.
- `production_branch` (string): Branch that deployments read. Defaults to the repository's default branch. A branch that GitHub renamed is stored under its new name. Maximum length: `255`. Example: `trunk`.

### Example request

```bash
curl -X PUT "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/repository" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "repository": "cookbook"
  }'
```

```http
PUT /v1/orgs/<orgId>/projects/<projectId>/repository HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 30

{
  "repository": "cookbook"
}
```

### Responses

**200 OK**

Project connected

Content type: `application/json`

- `project_id` (integer, required): The project ID
- `repository` (string, required): The repository as `owner/name`
- `repository_id` (integer, required): The GitHub repository ID
- `root_directory` (string | null, required): The stored root directory. Null is the repository root.
- `production_branch` (string, required): The branch that deployments read
- `connected_at` (string (date-time), required): When the project was connected
- `has_spicepod` (boolean, required): True when spicepod.yaml or spicepod.yml exists in the root directory on the production branch. A deployment fails until it does.

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found, or the organization's GitHub App installation cannot read the repository (code: `repository_not_found`)

**409 Conflict**

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 Content Too Large**

Request body too large

**422 Unprocessable Content**

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 Internal Server Error**

Internal server error

**503 Service Unavailable**

GitHub did not answer (code: `github_unavailable`)

## Get project metrics

`GET /v1/orgs/{orgId}/projects/{projectId}/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.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project

### Query parameters

- `window` (string, optional): 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.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/metrics" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/orgs/<orgId>/projects/<projectId>/metrics HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

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 type: `application/json`

- `ingestion` (object): Aggregated data ingestion metrics
  - `rows_ingested` (number): Total rows ingested (without window) or rows per second (with window)
  - `bytes_ingested` (number): Total bytes ingested (without window) or bytes per second (with window)
- `cluster` (object): Cluster metrics for the project
  - `active_executors_count` (number): Number of active executors registered with the scheduler
- `metrics` (map of object): Resource metrics keyed by pod name
  - `cpu_usage_percent` (number): CPU usage as a percentage (0-100)
  - `memory_usage_bytes` (number): Memory usage in bytes
  - `disk_read_bytes` (number): Disk bytes read per second (with window) or cumulative (without window)
  - `disk_read_operations` (number): Disk read operations per second (with window) or cumulative count (without window)
  - `disk_write_bytes` (number): Disk bytes written per second (with window) or cumulative (without window)
  - `disk_write_operations` (number): Disk write operations per second (with window) or cumulative count (without window)

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project or organization not found

**422 Unprocessable Content**

Organization is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

Metrics service unavailable

## List project instances

`GET /v1/orgs/{orgId}/projects/{projectId}/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`.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/instances" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/orgs/<orgId>/projects/<projectId>/instances HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

The project's instances

Content type: `application/json`

- `instances` (array of object, required)
  - `name` (string, required): The instance name: a Spicepod instance (pod) name, or a Cloud Connect runtime id. Pass it as `instanceName` to read its logs.
  - `status` (string, required): A Spicepod instance is `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, or `failed_to_start`. A Cloud Connect runtime is `online` or `offline`. Allowed values: `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, `failed_to_start`, `online`, `offline`.
  - `started_at` (string (date-time) | null, required): When the Spicepod instance started, or when the Cloud Connect runtime enrolled.

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**409 Conflict**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required): Allowed values: `project_not_deployed`, `project_paused`.

**422 Unprocessable Content**

The project is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

The project runtime did not answer

## Get project instance logs

`GET /v1/orgs/{orgId}/projects/{projectId}/instances/{instanceName}/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`.

### Path parameters

- `orgId` (integer, required): The org id from GET /v1/orgs. Minimum: `1`.
- `projectId` (integer, required): The ID of the project
- `instanceName` (string, required): The instance name from `GET /v1/projects/{projectId}/instances`

### Query parameters

- `tail` (integer, optional): How many of the most recent lines to return. Default: `100`. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): When present, X-Org-Name must name the same org as orgId. Otherwise the request returns org\_path\_mismatch (400). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/orgs/<orgId>/projects/<projectId>/instances/<instanceName>/logs" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/orgs/<orgId>/projects/<projectId>/instances/<instanceName>/logs HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

The instance's most recent log lines

Content type: `application/json`

- `instance` (string, required): The instance name
- `tail` (integer, required): The number of lines asked for
- `lines` (array of string, required): Log lines, oldest first

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required)
- `max_tail` (integer)

**404 Not Found**

The project was not found, or the instance is not one of the project's instances (`code` is `instance_not_found`).

**409 Conflict**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required): Allowed values: `project_not_deployed`, `project_paused`, `instance_offline`.

**422 Unprocessable Content**

The project is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

The project runtime did not answer

## List projects

`GET /v1/apps` (deprecated)

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.

### Query parameters

- `limit` (integer, optional): Maximum number of projects to return. Default: `1000`. Minimum: `1`. Maximum: `1000`.
- `offset` (integer, optional): Number of projects to skip when a cursor is not supplied. Default: `0`. Minimum: `0`.
- `after_id` (integer, optional): Return projects with a strictly smaller id than this value. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/apps" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/apps HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

List of projects

Content type: `application/json`

- `apps` (array of object)
  - `id` (integer): Unique identifier for the project
  - `name` (string): Name of the project
  - `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
  - `description` (string | null): Description of the project
  - `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
  - `created_at` (string (date-time)): Timestamp when the project was created
  - `cname` (string | null): Region identifier
  - `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
  - `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
  - `tags` (map of string | null): Key-value tags for the project

**400 Bad Request**

Invalid limit, offset, or after\_id

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

## Create a project

`POST /v1/apps` (deprecated)

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

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `name` (string, required): Project name (letters, numbers, hyphens only). Minimum length: `4`. Pattern: `^[a-zA-Z0-9-]+$`.
- `region` (string): 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. Allowed values: `us-east-1`, `us-west-2`.
- `cname` (string): **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.
- `cluster_name` (string): 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.
- `description` (string): Project description
- `visibility` (string): Allowed values: `public`, `private`. Default: `private`.
- `default_alerts` (boolean): Create default monitoring alerts for the new managed project. False skips initial setup. Manual alert creation remains available. Default: `true`.
- `tags` (map of string): Key-value tags for the project
- `update_channel` (string): Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking. Allowed values: `stable`, `preview`, `nightly`.
- `replicas` (integer): Number of replicas for a managed project. Rejected for standalone projects. Minimum: `0`.
- `resources` (object): Resource requests and limits for a managed project container. Rejected for standalone projects.
  - `limits` (object)
    - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
    - `memory` (string): Memory limit in Gi (for example, 16Gi)
    - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
  - `requests` (object)
    - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
- `executor` (object): Executor container configuration for a managed project. Rejected for standalone projects.
  - `replicas` (integer): Number of executor replicas. Minimum: `0`.
  - `resources` (object): Resource requests and limits for the executor container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/apps" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string"
  }'
```

```http
POST /v1/apps HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 22

{
  "name": "string"
}
```

### Responses

**201 Created**

Project created successfully. The response may include a `warning` field if initial configuration could not be applied.

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB

**400 Bad Request**

Invalid request body or invalid region

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity

**404 Not Found**

Organization not found, including an organization deleted while the project was being created

**409 Conflict**

A project with this name already exists, or the project was deleted while its initial configuration was being written (`code` is `project_deleted`)

**429 Too Many Requests**

Too many project creation requests (rate limited)

**500 Internal Server Error**

Project creation failed, including when the name-collision check could not be read

Content type: `application/json`

- `error` (string, required): Generic failure reason; the underlying cause stays in the server logs. Example: `Failed to create project`.

## Get a project

`GET /v1/apps/{appId}` (deprecated)

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

### Path parameters

- `appId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/apps/<appId>" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/apps/<appId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project details with configuration

Content type: `application/json`

- `id` (integer)
- `name` (string)
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string)
- `visibility` (string): Allowed values: `public`, `private`.
- `created_at` (string (date-time))
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string)
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string)
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `config` (object)
  - `spicepod` (object)
  - `image_tag` (string): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime, or null when Spice operates this project's runtime updates. Allowed values: `stable`, `preview`, `nightly`.
  - `version` (string): Semver range for the runtime version (e.g. 1.x, 2.x)
  - `replicas` (integer)
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
    - `storage_size_gb` (number | null)
  - `region` (string | null): Server-resolved region; null before a standalone instance is attached.
  - `storage_size_gb` (number | null)
  - `storage_claim_size_gb` (number, deprecated): Deprecated. Use storage\_size\_gb instead.

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## Update a project

`PUT /v1/apps/{appId}` (deprecated)

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.

### Path parameters

- `appId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `description` (string)
- `visibility` (string): Allowed values: `public`, `private`.
- `production_branch` (string)
- `tags` (map of string): Key-value tags for the project. The map replaces the stored tags. System tags the project already has stay on the project when the map omits them.
- `spicepod` (string | object): 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` (string | null): 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` (string): Update channel for a managed runtime; rejected for standalone projects. Allowed values: `stable`, `preview`, `nightly`.
- `version` (string): Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects.
- `replicas` (integer): Managed runtime replica count; rejected for standalone projects. Minimum: `0`.
- `resources` (object): Resource requests and limits for a managed project container; rejected for standalone projects.
  - `limits` (object)
    - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
    - `memory` (string): Memory limit in Gi (for example, 16Gi)
    - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
  - `requests` (object)
    - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
- `executor` (object): Executor container configuration for a managed project; rejected for standalone projects.
  - `replicas` (integer): Number of executor replicas. Minimum: `0`.
  - `resources` (object): Resource requests and limits for the executor container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string): Ephemeral storage limit in Gi (for example, 8Gi)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `storage_size_gb` (number)
- `region` (string): Managed project region; standalone placement is resolved during attachment.
- `cluster_name` (string): 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.
- `storage_size_gb` (number): Managed runtime storage size; rejected for standalone projects.
- `storage_claim_size_gb` (number, deprecated): Deprecated. Use storage\_size\_gb instead. Rejected for standalone projects.

### Example request

```bash
curl -X PUT "https://api.spice.ai/v1/apps/<appId>" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "string",
    "visibility": "public",
    "production_branch": "string",
    "tags": {},
    "spicepod": "string",
    "image_tag": "string",
    "update_channel": "stable",
    "version": "string",
    "replicas": 0,
    "resources": {},
    "executor": {},
    "region": "string",
    "cluster_name": "string",
    "storage_size_gb": 0,
    "storage_claim_size_gb": 0
  }'
```

```http
PUT /v1/apps/<appId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 362

{
  "description": "string",
  "visibility": "public",
  "production_branch": "string",
  "tags": {},
  "spicepod": "string",
  "image_tag": "string",
  "update_channel": "stable",
  "version": "string",
  "replicas": 0,
  "resources": {},
  "executor": {},
  "region": "string",
  "cluster_name": "string",
  "storage_size_gb": 0,
  "storage_claim_size_gb": 0
}
```

### Responses

**200 OK**

Project updated successfully

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

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 Not Found**

Project not found

**409 Conflict**

The project's lifecycle state changed while the update was in flight (a pause, resume, or restore landed at the same time), 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 Internal Server Error**

Internal server error

**502 Bad Gateway**

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 Service Unavailable**

Runtime version catalog is unavailable

## Delete a project

`DELETE /v1/apps/{appId}` (deprecated)

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

### Path parameters

- `appId` (integer, required): The ID of the project to delete

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X DELETE "https://api.spice.ai/v1/apps/<appId>" \
  -H "Authorization: Bearer <token>"
```

```http
DELETE /v1/apps/<appId> HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**204 No Content**

Project deleted successfully

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope or viewer role cannot mutate projects

**404 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## List forks of a project

`GET /v1/apps/{appId}/forks` (deprecated)

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`.

### Path parameters

- `appId` (integer, required): The ID of the source project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/apps/<appId>/forks" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/apps/<appId>/forks HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Forks of the project

Content type: `application/json`

- `forks` (array of object, required)
  - `id` (integer): Unique identifier for the project
  - `name` (string): Name of the project
  - `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
  - `description` (string | null): Description of the project
  - `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
  - `created_at` (string (date-time)): Timestamp when the project was created
  - `cname` (string | null): Region identifier
  - `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
  - `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
  - `tags` (map of string | null): Key-value tags for the project

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**500 Internal Server Error**

Internal server error

## Fork a project

`POST /v1/apps/{appId}/forks` (deprecated)

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.

### Path parameters

- `appId` (integer, required): The ID of the project to fork

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Optional.

Content type: `application/json`

- `name` (string): Name of the fork. Defaults to the source name with `-fork`, then `-fork-2` and so on, whichever is free. Minimum length: `4`. Maximum length: `38`. Pattern: `^[a-zA-Z0-9-]+$`.
- `region` (string): Region to place the fork in. Omit it and `cluster_name` to place the fork where the source runs. Allowed values: `us-east-1`, `us-west-2`.
- `cluster_name` (string): 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.
- `scheduler_state_location` (string): 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. Maximum length: `2048`.
- `default_alerts` (boolean): Create default monitoring alerts for the new managed fork. False skips initial setup. Manual alert creation remains available. Default: `true`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/apps/<appId>/forks" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string",
    "region": "us-east-1",
    "cluster_name": "string",
    "scheduler_state_location": "string",
    "default_alerts": true
  }'
```

```http
POST /v1/apps/<appId>/forks HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 141

{
  "name": "string",
  "region": "us-east-1",
  "cluster_name": "string",
  "scheduler_state_location": "string",
  "default_alerts": true
}
```

### Responses

**201 Created**

Project forked. It has not been deployed yet.

Content type: `application/json`

- `id` (integer): Unique identifier for the project
- `name` (string): Name of the project
- `kind` (string): Resolved project hosting model. Allowed values: `managed`, `standalone`, `byoc`.
- `description` (string | null): Description of the project
- `visibility` (string): Visibility setting for the project. Allowed values: `public`, `private`.
- `created_at` (string (date-time)): Timestamp when the project was created
- `cname` (string | null): Region identifier
- `cluster_name` (string | null): Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster.
- `endpoint` (string | null): The data-plane endpoint the project connects to, or null before a standalone instance is attached.
- `production_branch` (string | null): Production branch for the project
- `api_key` (string, deprecated): Deprecated. Use the API Keys endpoints instead.
- `tags` (map of string | null): Key-value tags for the project
- `forked_from` (object | null): 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.
  - `id` (integer, required): ID of the source project
  - `name` (string, required): Name of the source project
- `paused_at` (string (date-time) | null): Timestamp when the project was paused; null while running
- `warning` (string | null): Present when the project was created but initial configuration could not be applied
- `config` (object | null): Runtime configuration; null for an unattached standalone project.
  - `spicepod` (object | null): Spicepod configuration
  - `registry` (string | null): Container registry for the runtime image
  - `image_tag` (string | null): Runtime image tag
  - `update_channel` (string | null): Update channel for the runtime. Allowed values: `stable`, `preview`, `nightly`.
  - `replicas` (integer): Number of replicas
  - `resources` (object | null): Resource requests and limits for the project container
    - `limits` (object)
      - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
      - `memory` (string): Memory limit in Gi (for example, 16Gi)
      - `ephemeral-storage` (string)
    - `requests` (object)
      - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `executor` (object | null): Executor container configuration
    - `replicas` (integer): Number of executor replicas
    - `resources` (object): Resource requests and limits for the executor container
      - `limits` (object)
        - `cpu` (string): Whole-number vCPU limit, or '-' for no CPU limit
        - `memory` (string): Memory limit in Gi (for example, 16Gi)
        - `ephemeral-storage` (string)
      - `requests` (object)
        - `cpu` (string): CPU request. Minimum 100 millicores (for example, 100m or 0.1).
  - `region` (string | null): Cloud region code (e.g., us-east-1)
  - `node_group` (string | null): Node group for scheduling the project runtime
  - `storage_claim_size_gb` (number | null): Storage claim size in GB
- `shared_state` (array of object, required): 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.
  - `component` (string, required): The dataset, or `snapshots`
  - `setting` (string, required): Example: `pg_replication_slot`.
  - `value` (string, required)

**400 Bad Request**

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`).

**401 Unauthorized**

Unauthorized

**402 Payment Required**

The source runs models on hosted AI credits, which the organization has used up (`code: ai_credits_exhausted`).

**403 Forbidden**

Insufficient scope, a viewer role, or the source runs more replicas or executors than the plan or the dedicated cluster allows

**404 Not Found**

Project or organization not found

**409 Conflict**

A project with the requested name already exists, another project creation is in progress, or every default name is taken (`code: fork_name_unavailable`)

**413 Content Too Large**

Request body too large

**422 Unprocessable Content**

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`)

**429 Too Many Requests**

Too many project creation requests (rate limited)

**500 Internal Server Error**

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.

**502 Bad Gateway**

The source is connected to a GitHub repository, and its spicepod could not be read from it (`code: fork_source_repository_unreadable`)

## Pause a project

`POST /v1/apps/{appId}/pause` (deprecated)

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`.

### Path parameters

- `appId` (integer, required): The ID of the project to pause

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/apps/<appId>/pause" \
  -H "Authorization: Bearer <token>"
```

```http
POST /v1/apps/<appId>/pause HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project paused

Content type: `application/json`

- `id` (integer, required): The project ID
- `name` (string, required): The project name
- `paused_at` (string (date-time), required): Timestamp when the project was paused

**400 Bad Request**

Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**409 Conflict**

Project is already paused

Content type: `application/json`

- `error` (string, required)
- `paused_at` (string (date-time), required): Timestamp of the existing pause

**500 Internal Server Error**

Internal server error

## Resume a paused project

`POST /v1/apps/{appId}/resume` (deprecated)

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`.

### Path parameters

- `appId` (integer, required): The ID of the project to resume

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl -X POST "https://api.spice.ai/v1/apps/<appId>/resume" \
  -H "Authorization: Bearer <token>"
```

```http
POST /v1/apps/<appId>/resume HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

Project resumed, deployment created

Content type: `application/json`

- `id` (integer, required): The project ID
- `name` (string, required): The project name
- `paused_at` (string (date-time) | null, required): Always null after a successful resume
- `deployment_id` (integer, required): ID of the deployment created to bring the runtime back up

**400 Bad Request**

Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or the spicepod `name`, `kind` or `version` is missing or not valid (the body carries an `issues` array of `{path, message}`). 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.

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope, or the org's GitHub App installation cannot read the connected repository (`code: github_unauthorized` or `github_forbidden`). The project stays paused.

**404 Not Found**

Project not found, or the connected GitHub repository or its production branch was not found (`code: github_not_found`). The project stays paused.

**409 Conflict**

Project is not paused, or its spicepod was deleted after the request read it. Also returned when another deployment of the project is queued (`code: deployment_in_progress`); the project then stays paused. Also returned when the project's repository, root directory, or production branch changes while the spicepod is read from the repository; the project then stays paused. Resume again.

**422 Unprocessable Content**

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`). Also returned when the spicepod uses a connection credential that holds a secret reference deploy cannot resolve (`code: connection_credentials_unresolved`, with an `unresolved_credentials` array of `{connection, field}`). The project stays paused.

**429 Too Many Requests**

GitHub rate-limited the read of the connected repository (`code: github_rate_limited`). The project stays paused.

**500 Internal Server Error**

Internal server error

**502 Bad Gateway**

GitHub did not answer while the connected repository was read (`code: github_unavailable`). The project stays paused.

**503 Service Unavailable**

The runtime version catalog is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.

## Connect a project to a GitHub repository

`PUT /v1/apps/{appId}/repository` (deprecated)

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`.

### Path parameters

- `appId` (integer, required): The ID of the project to connect

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Request body

Required.

Content type: `application/json`

- `repository` (string, required): Repository in the organization's GitHub account, as `name` or `owner/name`. The owner must be the organization's GitHub account. Example: `cookbook`.
- `root_directory` (string | null): Directory inside the repository that contains spicepod.yaml. Omit it, or send null or an empty string, for the repository root. Maximum length: `512`. Example: `cayenne`.
- `production_branch` (string): Branch that deployments read. Defaults to the repository's default branch. A branch that GitHub renamed is stored under its new name. Maximum length: `255`. Example: `trunk`.

### Example request

```bash
curl -X PUT "https://api.spice.ai/v1/apps/<appId>/repository" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "repository": "cookbook"
  }'
```

```http
PUT /v1/apps/<appId>/repository HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 30

{
  "repository": "cookbook"
}
```

### Responses

**200 OK**

Project connected

Content type: `application/json`

- `project_id` (integer, required): The project ID
- `repository` (string, required): The repository as `owner/name`
- `repository_id` (integer, required): The GitHub repository ID
- `root_directory` (string | null, required): The stored root directory. Null is the repository root.
- `production_branch` (string, required): The branch that deployments read
- `connected_at` (string (date-time), required): When the project was connected
- `has_spicepod` (boolean, required): True when spicepod.yaml or spicepod.yml exists in the root directory on the production branch. A deployment fails until it does.

**400 Bad Request**

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 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope, or viewer role cannot mutate projects

**404 Not Found**

Project not found, or the organization's GitHub App installation cannot read the repository (code: `repository_not_found`)

**409 Conflict**

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 Content Too Large**

Request body too large

**422 Unprocessable Content**

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 Internal Server Error**

Internal server error

**503 Service Unavailable**

GitHub did not answer (code: `github_unavailable`)

## Get project metrics

`GET /v1/apps/{appId}/metrics` (deprecated)

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.

### Path parameters

- `appId` (integer, required): The ID of the project

### Query parameters

- `window` (string, optional): 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.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/apps/<appId>/metrics" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/apps/<appId>/metrics HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

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 type: `application/json`

- `ingestion` (object): Aggregated data ingestion metrics
  - `rows_ingested` (number): Total rows ingested (without window) or rows per second (with window)
  - `bytes_ingested` (number): Total bytes ingested (without window) or bytes per second (with window)
- `cluster` (object): Cluster metrics for the project
  - `active_executors_count` (number): Number of active executors registered with the scheduler
- `metrics` (map of object): Resource metrics keyed by pod name
  - `cpu_usage_percent` (number): CPU usage as a percentage (0-100)
  - `memory_usage_bytes` (number): Memory usage in bytes
  - `disk_read_bytes` (number): Disk bytes read per second (with window) or cumulative (without window)
  - `disk_read_operations` (number): Disk read operations per second (with window) or cumulative count (without window)
  - `disk_write_bytes` (number): Disk bytes written per second (with window) or cumulative (without window)
  - `disk_write_operations` (number): Disk write operations per second (with window) or cumulative count (without window)

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project or organization not found

**422 Unprocessable Content**

Organization is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

Metrics service unavailable

## List project instances

`GET /v1/apps/{appId}/instances` (deprecated)

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`.

### Path parameters

- `appId` (integer, required): The ID of the project

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/apps/<appId>/instances" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/apps/<appId>/instances HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

The project's instances

Content type: `application/json`

- `instances` (array of object, required)
  - `name` (string, required): The instance name: a Spicepod instance (pod) name, or a Cloud Connect runtime id. Pass it as `instanceName` to read its logs.
  - `status` (string, required): A Spicepod instance is `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, or `failed_to_start`. A Cloud Connect runtime is `online` or `offline`. Allowed values: `deploying`, `loading`, `ready`, `unhealthy`, `terminating`, `failed`, `failed_to_start`, `online`, `offline`.
  - `started_at` (string (date-time) | null, required): When the Spicepod instance started, or when the Cloud Connect runtime enrolled.

**400 Bad Request**

Invalid project ID

**401 Unauthorized**

Unauthorized

**403 Forbidden**

Insufficient scope

**404 Not Found**

Project not found

**409 Conflict**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required): Allowed values: `project_not_deployed`, `project_paused`.

**422 Unprocessable Content**

The project is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

The project runtime did not answer

## Get project instance logs

`GET /v1/apps/{appId}/instances/{instanceName}/logs` (deprecated)

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`.

### Path parameters

- `appId` (integer, required): The ID of the project
- `instanceName` (string, required): The instance name from `GET /v1/projects/{projectId}/instances`

### Query parameters

- `tail` (integer, optional): How many of the most recent lines to return. Default: `100`. Minimum: `1`.

### Header parameters

- `Authorization` (string, required): `Bearer <token>`. [Authentication](https://spice.ai/docs/cloud/api/management#authentication) lists the tokens it accepts.
- `X-Org-Name` (string, optional): 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). Pattern: `^[A-Za-z0-9._-]{1,255}$`.

### Example request

```bash
curl "https://api.spice.ai/v1/apps/<appId>/instances/<instanceName>/logs" \
  -H "Authorization: Bearer <token>"
```

```http
GET /v1/apps/<appId>/instances/<instanceName>/logs HTTP/1.1
Host: api.spice.ai
Authorization: Bearer <token>
```

### Responses

**200 OK**

The instance's most recent log lines

Content type: `application/json`

- `instance` (string, required): The instance name
- `tail` (integer, required): The number of lines asked for
- `lines` (array of string, required): Log lines, oldest first

**400 Bad Request**

Invalid project ID or `tail`

**401 Unauthorized**

Unauthorized

**403 Forbidden**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required)
- `max_tail` (integer)

**404 Not Found**

The project was not found, or the instance is not one of the project's instances (`code` is `instance_not_found`).

**409 Conflict**

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 type: `application/json`

- `error` (string, required)
- `code` (string, required): Allowed values: `project_not_deployed`, `project_paused`, `instance_offline`.

**422 Unprocessable Content**

The project is not fully configured

**500 Internal Server Error**

Internal server error

**503 Service Unavailable**

The project runtime did not answer
