---
title: "Attach an existing project to an enrolled instance"
method: POST
path: "/v1/cloud-connect/attach"
tags: ["Cloud Connect"]
---

# Attach an existing project to an enrolled instance

`POST /v1/cloud-connect/attach`

## Request

- Base URL: `https://api.spice.ai`
- URL: `https://api.spice.ai/v1/cloud-connect/attach`
- Auth: HTTP bearer

Attaches an existing standalone project to an enrolled standalone instance and assigns the project's region and cname from the stamp whose gateway terminates that instance's control stream. The optional location field is reserved for multi-region use and is ignored today.
This operation is dual-authorized. A user-derived bearer plus the X-Org-Name assertion authorizes the project mutation; OAuth client credentials are refused. The caller must have the instances:write and apps:write scopes and the organization admin or owner role. The cert_pem and pop_sig body fields prove only that the request concerns the named instance. Instance proof by itself never authorizes project attachment.
pop_sig is a base64 DER ECDSA (SHA-256) signature over the UTF-8 bytes `spice-cloud-connect/attach/v1\n{X-Org-Name}\n{instance_id}\n{project_id}`. The project must already have kind standalone and no cluster target. The database is the authority for attachment conflicts; clients must not pre-check or infer an attachment limit from this contract.
Errors use `{code, error, retryable}`. A 409 always has `retryable: false`; a 5xx has `retryable: true` so the client exact replays any request whose attachment may already have committed.

## Headers

- `X-Org-Name` string, required

## Request body

- object
  - `instance_id` string, required: Enrolled instance caller-facing id (`inst_…`)
  - `project_id` integer, required: Existing standalone project id
  - `location` string: Reserved for multi-region use; accepted and ignored today
  - `cert_pem` string, required: Cloud Connect identity certificate for instance_id
  - `pop_sig` string, required: Base64 DER ECDSA signature over the documented attach challenge

## Response `200`

Project attached, or exact same instance/project replay repaired

- object
  - `instance_id` string, required
  - `organization` object, required
    - `id` integer, required
    - `name` string, required: Canonical organization name
  - `project` object, required
    - `id` integer, required
    - `name` string, required: Canonical project name
  - `monitor_url` string, uri, required: Credential-free portal monitoring link for the project

## Other responses

- `400`: Invalid body or X-Org-Name (`invalid_request` or `invalid_org_assertion`)
- `401`: Missing/invalid user bearer or invalid instance proof (`unauthenticated` or `invalid_instance_proof`)
- `403`: Non-user bearer, insufficient scope, org access, or role refusal (`forbidden`, `org_forbidden`, `org_assertion_mismatch`, or `org_deleted`)
- `404`: Instance or project not found in the authorized organization (`instance_not_found` or `project_not_found`)
- `409`: Instance not enrolled or already attached, project not standalone, the database reports the project is already attached, or the project moved to another org during the attach (`instance_not_enrolled`, `instance_already_attached`, `project_not_standalone`, `project_already_attached`, or `project_left_org`). These refusals carry `retryable: false` and prove this request did not commit an attachment.
- `413`: Request body exceeds 1 MiB (`invalid_request`)
- `500`: Internal failure, including post-commit routing repair (`internal`, retryable)

---

[API](https://skmtc.dev/spice/apis/spice-cloud-control-plane-api.md) - [All operations](https://skmtc.dev/spice/apis/spice-cloud-control-plane-api/llms.txt) - [OpenAPI document](https://skmtc.dev/spice/apis/spice-cloud-control-plane-api/revisions/5f3b75dd847d?raw)
