{"openapi":"3.1.0","servers":[{"url":"https://api.spice.ai"}],"info":{"title":"Spice Cloud Control-Plane API","version":"v1","description":"The Spice Cloud Platform API provides programmatic access to manage projects, deployments, secrets, API keys, and organization members on the Spice.ai cloud platform. Projects were previously called apps: every `/v1/projects` endpoint is also served at the legacy `/v1/apps` path.\n\n## Organization context\n\nA request acts on one organization. By default that is the organization the credential was minted against; send `X-Org-Name` with an organization handle to act on another. A user credential may name any organization its owner belongs to. A machine credential (OAuth client credentials) is issued to one organization and stays pinned to it: naming another is refused, never ignored.\n\nRefusals carry a machine-readable `code` alongside `error`, because the remedies differ: `org_forbidden` means the caller is not a member of the named organization (ask an owner for an invitation), `forbidden` means they are a member but lack the role the action needs, `insufficient_scope` means the credential was not granted it, `org_assertion_mismatch` means a machine credential named an organization other than its own, `org_deleted` means the organization the credential itself is bound to has been deleted (no invitation or role reaches it again), and `invalid_org_assertion` (400) means the header was not a valid handle."},"paths":{"/v1/health":{"get":{"summary":"Health check","description":"Returns the health status of the API.","responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"timestamp":{"type":"string","format":"date-time"}}}}}}},"tags":["Health"]}},"/v1/cloud-connect/token":{"post":{"summary":"Issue a Cloud Connect enrollment token","description":"Issues a one-time standalone or cluster enrollment token. Requires a user-derived bearer, X-Org-Name, the kind-appropriate write scope, and org admin/owner role. Client-credential tokens are rejected because enrollment admission must retain user audit attribution.","security":[{"BearerAuth":[]}],"parameters":[{"in":"header","name":"X-Org-Name","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["standalone"]},"ttl_seconds":{"type":"integer","minimum":1,"maximum":86400,"default":3600}}},{"type":"object","additionalProperties":false,"required":["kind","name"],"properties":{"kind":{"type":"string","enum":["cluster"]},"name":{"type":"string"},"region":{"type":"string"},"cloud_account_id":{"type":"string"},"cloud_provider":{"type":"string","enum":["aws"]},"operations_mode":{"type":"string","enum":["managed","approval_required"]},"ttl_seconds":{"type":"integer","minimum":1,"maximum":86400,"default":3600}}},{"type":"object","additionalProperties":false,"required":["kind","connection_id"],"properties":{"kind":{"type":"string","enum":["cluster"]},"connection_id":{"type":"string"},"ttl_seconds":{"type":"integer","minimum":1,"maximum":86400,"default":3600}}}]}}}},"responses":{"201":{"description":"Enrollment token issued","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["kind","token","expires_at","organization","target"],"properties":{"kind":{"type":"string","enum":["standalone","cluster"]},"token":{"type":"string","pattern":"^spice-enroll-[A-Za-z0-9_-]{32}$"},"expires_at":{"type":"string","format":"date-time"},"organization":{"type":"object","additionalProperties":false,"required":["id","name"],"properties":{"id":{"type":"integer"},"name":{"type":"string"}}},"target":{"oneOf":[{"type":"object","description":"The created cluster or rotated connection identifier."},{"type":"null"}]}}}}}},"400":{"description":"Invalid request or missing organization assertion"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, role, or plan"},"409":{"description":"Cluster has already enrolled"},"413":{"description":"Request body too large"},"429":{"description":"Token issuance rate limit exceeded"},"500":{"description":"Internal token issuance failure"}},"tags":["Cloud Connect"]}},"/v1/cloud-connect/enroll":{"post":{"summary":"Enroll a Cloud Connect cluster or standalone runtime","description":"Token enrollment accepts a canonical kind-bound enrollment token. Standalone enrollment may instead use user Authorization plus X-Org-Name. Idempotency-Key is required for standalone enrollment.","security":[{},{"BearerAuth":[]}],"parameters":[{"in":"header","name":"Idempotency-Key","required":false,"schema":{"type":"string","format":"uuid"}},{"in":"header","name":"X-Org-Name","required":false,"description":"Required with Bearer authorization for authenticated standalone enrollment; omitted for token enrollment.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["kind","token","csr_pem","enc_pubkey_pem","instance"],"properties":{"kind":{"type":"string","enum":["standalone"]},"token":{"type":"string","pattern":"^spice-enroll-[A-Za-z0-9_-]{32}$"},"expected_org":{"type":"string"},"csr_pem":{"type":"string"},"enc_pubkey_pem":{"type":"string"},"instance":{"type":"object"},"region":{"type":"string"}}},{"type":"object","additionalProperties":false,"required":["kind","token","csr_pem","enc_pubkey_pem"],"properties":{"kind":{"type":"string","enum":["cluster"]},"token":{"type":"string","pattern":"^spice-enroll-[A-Za-z0-9_-]{32}$"},"expected_org":{"type":"string"},"csr_pem":{"type":"string"},"enc_pubkey_pem":{"type":"string"}}},{"type":"object","additionalProperties":false,"required":["kind","csr_pem","enc_pubkey_pem","instance"],"properties":{"kind":{"type":"string","enum":["standalone"]},"csr_pem":{"type":"string"},"enc_pubkey_pem":{"type":"string"},"instance":{"type":"object"},"region":{"type":"string"}}}]}}}},"responses":{"200":{"description":"Enrollment completed or exactly replayed","content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["kind","instance_id","identity_cert_pem","ca_bundle_pem","gateway_addr","not_after","organization","region","portal","attachment","recovered"],"properties":{"kind":{"type":"string","enum":["standalone"]},"instance_id":{"type":"string"},"identity_cert_pem":{"type":"string"},"ca_bundle_pem":{"type":"string"},"gateway_addr":{"type":"string"},"not_after":{"type":"string","format":"date-time"},"organization":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer"},"name":{"type":"string"}}},"region":{"type":"string"},"portal":{"type":"object","required":["new_project_url"],"properties":{"new_project_url":{"type":"string","format":"uri"}}},"attachment":{"type":"null"},"recovered":{"type":"boolean"}}},{"type":"object","additionalProperties":false,"required":["kind","connection_id","identity_cert_pem","ca_bundle_pem","gateway_addr","not_after","organization","region","attachment"],"properties":{"kind":{"type":"string","enum":["cluster"]},"connection_id":{"type":"string"},"identity_cert_pem":{"type":"string"},"ca_bundle_pem":{"type":"string"},"gateway_addr":{"type":"string"},"not_after":{"type":"string","format":"date-time"},"organization":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer"},"name":{"type":"string"}}},"region":{"type":"string"},"attachment":{"type":"null"}}}]}}}},"400":{"description":"Invalid request, kind mismatch, or unsupported project field"},"401":{"description":"Invalid credential or authorization"},"403":{"description":"Authenticated caller lacks the required scope or organization role"},"409":{"description":"Consumed token, connection already enrolled, unavailable connection, organization mismatch, or idempotency mismatch"},"410":{"description":"Enrollment token or authenticated recovery window expired"},"413":{"description":"Request body too large"},"500":{"description":"Internal or identity-provider failure; use the retryable response field"}},"tags":["Cloud Connect"]}},"/v1/cloud-connect/attach":{"post":{"summary":"Attach an existing project to an enrolled instance","description":"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.\nThis 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.\npop_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.\nErrors 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.\n","security":[{"BearerAuth":[]}],"parameters":[{"in":"header","name":"X-Org-Name","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"},"description":"Organization that owns both the project and instance; also bound into pop_sig"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["instance_id","project_id","cert_pem","pop_sig"],"properties":{"instance_id":{"type":"string","minLength":1,"maxLength":200,"description":"Enrolled instance caller-facing id (`inst_…`)"},"project_id":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Existing standalone project id"},"location":{"type":"string","minLength":1,"maxLength":200,"description":"Reserved for multi-region use; accepted and ignored today"},"cert_pem":{"type":"string","minLength":1,"maxLength":8192,"description":"Cloud Connect identity certificate for instance_id"},"pop_sig":{"type":"string","minLength":1,"maxLength":512,"description":"Base64 DER ECDSA signature over the documented attach challenge"}}}}}},"responses":{"200":{"description":"Project attached, or exact same instance/project replay repaired","content":{"application/json":{"schema":{"type":"object","required":["instance_id","organization","project","monitor_url"],"properties":{"instance_id":{"type":"string"},"organization":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer"},"name":{"type":"string","description":"Canonical organization name"}}},"project":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer"},"name":{"type":"string","description":"Canonical project name"}}},"monitor_url":{"type":"string","format":"uri","pattern":"^https://","description":"Credential-free portal monitoring link for the project"}}}}}},"400":{"description":"Invalid body or X-Org-Name (`invalid_request` or `invalid_org_assertion`)","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}},"401":{"description":"Missing/invalid user bearer or invalid instance proof (`unauthenticated` or `invalid_instance_proof`)","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}},"403":{"description":"Non-user bearer, insufficient scope, org access, or role refusal (`forbidden`, `org_forbidden`, `org_assertion_mismatch`, or `org_deleted`)","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}},"404":{"description":"Instance or project not found in the authorized organization (`instance_not_found` or `project_not_found`)","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}},"409":{"description":"Instance not enrolled or already attached, project not standalone, or the database reports the project is already attached (`instance_not_enrolled`, `instance_already_attached`, `project_not_standalone`, or `project_already_attached`). These refusals carry `retryable: false` and prove this request did not commit an attachment.\n","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}},"413":{"description":"Request body exceeds 1 MiB (`invalid_request`)","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}},"500":{"description":"Internal failure, including post-commit routing repair (`internal`, retryable)","content":{"application/json":{"schema":{"type":"object","required":["code","error","retryable"],"properties":{"code":{"type":"string","enum":["invalid_request","invalid_org_assertion","unauthenticated","forbidden","org_forbidden","org_assertion_mismatch","org_deleted","invalid_instance_proof","instance_not_found","instance_not_enrolled","instance_already_attached","project_not_found","project_not_standalone","project_already_attached","internal"]},"error":{"type":"string"},"retryable":{"type":"boolean"}}}}}}},"tags":["Cloud Connect"]}},"/v1/cloud-connect/attachable-projects":{"get":{"summary":"List projects available for Cloud Connect attachment","description":"Returns every project the authenticated caller may attach an enrolled Cloud Connect instance to. Policy is evaluated entirely by the server: today, results include only live, unattached standalone projects in the resolved organization, and user credentials require that organization's owner or admin role. Exactly one organization is searched per request and the response names it in `org`, so an empty `projects` states which organization has no eligible project rather than leaving a client to wonder whether the one it meant was searched at all; name another with `X-Org-Name`. Clients must render every returned project and instance without applying their own eligibility filters. The `instances` array is part of the stable contract and may be populated when server policy permits projects with existing attachments.\n","security":[{"BearerAuth":[]}],"parameters":[{"in":"header","name":"X-Org-Name","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"},"description":"Organization to list. A user token may name any organization its owner belongs to; naming one they do not is `org_forbidden`. Never ignored: a token bound to one organization that names another is `org_assertion_mismatch`.\n"},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Maximum number of projects to return."},{"in":"query","name":"cursor","required":false,"schema":{"type":"string"},"description":"Opaque cursor returned as `next_cursor` by the previous page."}],"responses":{"200":{"description":"A page of projects currently eligible for attachment.","content":{"application/json":{"schema":{"type":"object","required":["org","projects","next_cursor"],"properties":{"org":{"type":"string","description":"Handle of the organization the page was scoped to, being the one `X-Org-Name` named or the credential's own when it named none. Always present, so an empty `projects` says which organization has no eligible project rather than leaving a client to guess whether its active organization was the one searched.\n"},"projects":{"type":"array","items":{"type":"object","required":["id","name","org","region","instances"],"properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"org":{"type":"string","description":"Organization handle."},"region":{"type":["string","null"],"description":"Project control-plane region, if assigned."},"instances":{"type":"array","items":{"type":"object","required":["id","location","enrolled_at"],"properties":{"id":{"type":"string","description":"Instance external identifier."},"location":{"type":["string","null"],"description":"Customer-declared instance location."},"enrolled_at":{"type":["string","null"],"format":"date-time"}}}}}}},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or null at the end."}}}}}},"400":{"description":"Invalid organization assertion (`invalid_org_assertion`) or invalid limit/cursor (`invalid_pagination`).\n"},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Organization access (`org_forbidden`, `org_assertion_mismatch`, or `org_deleted`), apps:read scope (`insufficient_scope`), or owner/admin role (`forbidden`) was refused.\n"},"500":{"description":"Organization role, project, or organization-handle lookup failed.\n"}},"tags":["Cloud Connect"]}},"/v1/projects":{"get":{"summary":"List projects","description":"Returns all projects belonging to the authenticated organization.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"}},"tags":["Projects"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]},"post":{"summary":"Create a project","description":"Creates a new project in the authenticated organization.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":4,"pattern":"^[a-zA-Z0-9-]+$","description":"Project name (letters, numbers, hyphens only)"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Cloud region where a managed project will be deployed (e.g. `us-east-1`). Omit this, `cname`, and `cluster_name` to create an unattached standalone project whose placement is resolved when linked.\n"},"cname":{"type":"string","description":"**Deprecated.** Internal region CNAME (e.g. `us-east-1-prod-aws-data`). Use `region` instead. Accepted for backwards compatibility when `region` is omitted. Omit all region sources to create an unattached standalone project.\n"},"cluster_name":{"type":"string","description":"Name of the dedicated cluster to deploy to, as listed by `GET /v1/clusters`. Provide exactly one of `region` or `cluster_name`. When set, the region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Omit all region sources to create an unattached standalone project.\n"},"description":{"type":"string","description":"Project description"},"visibility":{"type":"string","enum":["public","private"],"default":"private"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking."},"replicas":{"type":"integer","minimum":0,"description":"Number of replicas for a managed project. Rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container. Rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project. Rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}}}}}}},"responses":{"201":{"description":"Project created successfully. The response may include a `warning` field if initial configuration could not be applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or invalid region"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity"},"409":{"description":"A project with this name already exists"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"Project creation failed, including when the name-collision check could not be read","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Generic failure reason; the underlying cause stays in the server logs","example":"Failed to create project"}}}}}}},"tags":["Projects"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]}},"/v1/projects/{projectId}":{"get":{"summary":"Get a project","description":"Returns details for a specific project, including its configuration.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project details with configuration","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"created_at":{"type":"string","format":"date-time"},"cluster_name":{"type":"string","nullable":true,"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":"string","nullable":true,"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":"string"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":"object","additionalProperties":{"type":"string"}},"paused_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp when the project was paused; null while running"},"config":{"type":"object","properties":{"spicepod":{"type":"object"},"image_tag":{"type":"string","description":"Runtime image tag"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"nullable":true,"description":"Update channel for the runtime, or null when Spice operates this project's runtime updates."},"version":{"type":"string","description":"Semver range for the runtime version (e.g. 1.x, 2.x)"},"replicas":{"type":"integer"},"resources":{"type":"object","nullable":true,"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","nullable":true,"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number","nullable":true}}},"region":{"type":"string","nullable":true,"description":"Server-resolved region; null before a standalone instance is attached."},"storage_size_gb":{"type":"number","nullable":true},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead."}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]},"put":{"summary":"Update a project","description":"Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"production_branch":{"type":"string"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"spicepod":{"oneOf":[{"type":"string","description":"YAML string of the spicepod configuration"},{"type":"object","description":"JSON object of the spicepod configuration"}]},"image_tag":{"type":"string","nullable":true,"description":"Runtime image tag for a managed project; rejected for standalone projects. A non-empty value pins the project. On stable or preview, send null to clear a pin so deploys resolve from the channel catalog (stable - latest GA for the configured version range; preview - latest RC). null is rejected on nightly and internal channels."},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed runtime; rejected for standalone projects."},"version":{"type":"string","description":"Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects."},"replicas":{"type":"integer","minimum":0,"description":"Managed runtime replica count; rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container; rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project; rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number"}}},"region":{"type":"string","description":"Managed project region; standalone placement is resolved during attachment."},"cluster_name":{"type":"string","description":"Reassign the project to a dedicated cluster, as listed by `GET /v1/clusters`. Provide at most one of `region` or `cluster_name`. When set, region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Rejected for standalone projects, whose placement is resolved during attachment.\n"},"storage_size_gb":{"type":"number","description":"Managed runtime storage size; rejected for standalone projects."},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead. Rejected for standalone projects."}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or project ID, including placement or hosted runtime fields that do not apply to standalone projects"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot mutate projects, requested replicas or resources exceed plan limits, dedicated-cluster assignment or special-org private-compute ceilings required, requested resources exceed dedicated-cluster capacity, `image_tag` was supplied outside the Enterprise plan (code: `image_tag_requires_enterprise`), or the requested runtime image is not available for the organization plan (code: `image_tag_not_allowed_for_plan`); omitting `image_tag` leaves the configured image unchanged"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"},"503":{"description":"Runtime version catalog is unavailable"}},"tags":["Projects"]},"delete":{"summary":"Delete a project","description":"Deletes a project and tears down its runtime resources.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to delete"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Project deleted successfully"},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot mutate projects"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"]}},"/v1/projects/{projectId}/pause":{"post":{"summary":"Pause a project","description":"Pauses the project's running spicepod. The runtime is torn down and the\nspicepod record is marked as paused. Settings and configuration are\npreserved. Resume with POST /v1/projects/{projectId}/resume. Requires\n`apps:write`.\n","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to pause"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project paused","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp when the project was paused"}}}}}},"400":{"description":"Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"Project is already paused","content":{"application/json":{"schema":{"type":"object","required":["error","paused_at"],"properties":{"error":{"type":"string"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp of the existing pause"}}}}}},"500":{"description":"Internal server error"}},"tags":["Projects"]}},"/v1/projects/{projectId}/resume":{"post":{"summary":"Resume a paused project","description":"Clears the paused state on the project's spicepod and creates a new\ndeployment to bring the runtime back up. Requires `apps:write`.\n","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to resume"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project resumed, deployment created","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at","deployment_id"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","nullable":true,"description":"Always null after a successful resume"},"deployment_id":{"type":"integer","description":"ID of the deployment created to bring the runtime back up"}}}}}},"400":{"description":"Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or spicepod configuration is invalid"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"Project is not paused"},"422":{"description":"No runtime version in the catalog matches this project’s version range (`code: version_no_match`).\n"},"500":{"description":"Internal server error"},"503":{"description":"The runtime version catalog (Edge Config) is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.\n"}},"tags":["Projects"]}},"/v1/projects/{projectId}/metrics":{"get":{"summary":"Get project metrics","description":"Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"window","required":false,"schema":{"type":"string"},"description":"Prometheus-style duration (e.g. `5m`, `1h`, `30s`). When provided, counter metrics are returned as rates over the window instead of raw cumulative values. For example, `rows_ingested` returns average rows per second with a window, or total cumulative rows without one."},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Resource and ingestion metrics. Missing fields indicate no data is currently available. Individual metric queries may fail independently; partial results are returned when possible.","content":{"application/json":{"schema":{"type":"object","properties":{"ingestion":{"type":"object","description":"Aggregated data ingestion metrics","properties":{"rows_ingested":{"type":"number","description":"Total rows ingested (without window) or rows per second (with window)"},"bytes_ingested":{"type":"number","description":"Total bytes ingested (without window) or bytes per second (with window)"}}},"cluster":{"type":"object","description":"Cluster metrics for the project","properties":{"active_executors_count":{"type":"number","description":"Number of active executors registered with the scheduler"}}},"metrics":{"type":"object","description":"Resource metrics keyed by pod name","additionalProperties":{"type":"object","properties":{"cpu_usage_percent":{"type":"number","description":"CPU usage as a percentage (0-100)"},"memory_usage_bytes":{"type":"number","description":"Memory usage in bytes"},"disk_read_bytes":{"type":"number","description":"Disk bytes read per second (with window) or cumulative (without window)"},"disk_read_operations":{"type":"number","description":"Disk read operations per second (with window) or cumulative count (without window)"},"disk_write_bytes":{"type":"number","description":"Disk bytes written per second (with window) or cumulative (without window)"},"disk_write_operations":{"type":"number","description":"Disk write operations per second (with window) or cumulative count (without window)"}}}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or organization not found"},"422":{"description":"Organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"Metrics service unavailable"}},"tags":["Projects"]}},"/v1/projects/{projectId}/deployments":{"get":{"summary":"List deployments","description":"Returns a list of deployments for the specified project, ordered by most recent first.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Maximum number of deployments to return"},{"in":"query","name":"status","schema":{"type":"string","enum":["queued","in_progress","succeeded","failed","created"]},"description":"Filter by deployment status"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of deployments","content":{"application/json":{"schema":{"type":"object","properties":{"deployments":{"type":"array","items":{"$ref":"#/components/schemas/Deployment"}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"}},"tags":["Deployments"]},"post":{"summary":"Create a deployment","description":"Creates a new deployment for the specified project using its current spicepod configuration. The runtime image is selected based on the project's update channel.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"image_tag":{"type":"string","description":"Override the runtime image tag for this deployment. Requires the Enterprise plan; omit it to deploy the image the project is configured with."},"channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel that determines the runtime image"},"replicas":{"type":"integer","minimum":1,"maximum":10,"description":"Override the number of replicas for this deployment"},"branch":{"type":"string","description":"Git branch name"},"commit_sha":{"type":"string","description":"Git commit SHA"},"commit_message":{"type":"string","description":"Git commit message"},"debug":{"type":"boolean","description":"Enable debug mode for this deployment"},"target":{"type":"object","description":"Optional per-deployment override. Omit (or `{ kind: cloud }`) to place the deployment where the project itself runs — Spice-managed compute, its own BYOC cluster, or its attached standalone Cloud Connect instance — which is a property of the project, not of this request. Pass `{ kind: cluster, instance_id }` to override that for this deployment and route it to a BYOC cluster connection; the cluster must be owned by the token's org, not in a terminal lifecycle state (decommissioning/archived), not paused, and the operator must hold a live connection (verified by the connectivity ping, not the Supabase lifecycle status). A standalone project cannot be overridden this way and is refused with 409.\n","properties":{"kind":{"type":"string","enum":["cloud","cluster"]},"instance_id":{"type":"string","description":"Required when kind is `cluster`."}}}}}}}},"responses":{"202":{"description":"Deployment created and queued for processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deployment"}}}},"400":{"description":"Invalid request body, project has no spicepod configuration, the spicepod has been deleted, or the spicepod is paused (resume it with POST /v1/projects/{projectId}/resume before deploying). For a standalone Cloud Connect project, also returned when the Spicepod references a `${secrets:...}` name the project does not define: the body carries `code: unknown_secret_references`, a `missing_secrets` array of `{name, path, suggestion?}`, and `secrets_url`. Nothing is dispatched and no deployment is recorded.\n"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot create deployments, `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` deploys the image the project is configured with"},"404":{"description":"Project not found — or, for a `cluster` target, the BYOC cluster connection does not exist or is not owned by the token's org.\n"},"409":{"description":"A deployment is already in progress for this project — or, for a `cluster` target, the BYOC cluster is paused, offline, or otherwise not in a deployable state. Also returned when the project's deploy target is unusable: its kind conflicts with its cluster/instance references, a `cluster` target was requested for a standalone project, a standalone project has no attached instance, or that instance has not yet enrolled the encryption key its secrets are sealed to (retrying once it is online succeeds).\n"},"413":{"description":"Request body exceeds the maximum size (1 MiB), or — for a standalone Cloud Connect project — the project's resolved secrets exceed the maximum sealed-payload size. Nothing is dispatched and no deployment is recorded.\n"},"422":{"description":"Organization is not fully configured, or no runtime version in the catalog matches this project’s version range (`code: version_no_match`).\n"},"500":{"description":"Internal server error. For a standalone Cloud Connect project, also returned with `code: deployment_not_settled` when the deployment record could not be moved out of `queued`; the project's next deployment is refused until it is cleared.\n"},"502":{"description":"Upstream service failure while provisioning the deployment, or — for a standalone Cloud Connect project — the spicepod could not be handed to the instance's gateway, or the instance rejected it.\n"},"503":{"description":"The runtime version catalog (Edge Config) is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview deploy cannot resolve an image tag — or catalog membership for an explicit `image_tag` override cannot be validated.\n"}},"tags":["Deployments"]}},"/v1/projects/{projectId}/deployments/{deploymentId}":{"get":{"summary":"Get a deployment","description":"Returns a single deployment for the specified project. The body matches one item from GET /v1/projects/{projectId}/deployments. Not cacheable. Poll this path for stored status after POST create.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"deploymentId","required":true,"schema":{"type":"integer"},"description":"The ID of the deployment"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deployment"}}}},"400":{"description":"Invalid project ID or deployment ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or deployment not found"},"500":{"description":"Internal server error"}},"tags":["Deployments"]}},"/v1/projects/{projectId}/secrets":{"get":{"summary":"List secrets","description":"Returns all secrets for the specified project. Secret values are always masked.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of secrets","content":{"application/json":{"schema":{"type":"object","properties":{"secrets":{"type":"array","items":{"$ref":"#/components/schemas/Secret"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot perform write operations"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"]},"post":{"summary":"Create or update a secret","description":"Creates a new secret or updates an existing secret with the same name. The secret value is encrypted at rest.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$","description":"Secret name (must start with a letter or underscore, alphanumeric and underscores only)"},"value":{"type":"string","description":"Secret value (will be encrypted at rest)"}}}}}},"responses":{"200":{"description":"Secret created or updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}}},"400":{"description":"Invalid request body or project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"]}},"/v1/projects/{projectId}/secrets/{secretName}":{"get":{"summary":"Get a secret","description":"Returns a specific secret by name. The secret value is always masked.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"secretName","required":true,"schema":{"type":"string"},"description":"The name of the secret"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Secret details (value is masked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or admin or owner role required"},"404":{"description":"Project or secret not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"]},"delete":{"summary":"Delete a secret","description":"Permanently deletes a secret by name.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"secretName","required":true,"schema":{"type":"string"},"description":"The name of the secret"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Secret deleted successfully"},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or secret not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"]}},"/v1/projects/{projectId}/api-keys":{"get":{"summary":"Get API keys","description":"Returns the API keys for a specific project. API keys are used to authenticate requests to the Spice.ai runtime.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"API keys for the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeys"}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot regenerate API keys"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["API Keys"]},"post":{"summary":"Regenerate an API key","description":"Regenerates an API key for the specified project. This invalidates the previous key. Use key_number to specify which key to regenerate (0 for both, 1 for primary, 2 for secondary).","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"key_number":{"type":"integer","enum":[0,1,2],"default":1,"description":"Which API key to regenerate (0 = both, 1 = primary, 2 = secondary)"}}}}}},"responses":{"200":{"description":"API key regenerated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeysRegenerated"}}}},"400":{"description":"Invalid request body or project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["API Keys"]}},"/v1/projects/{projectId}/reactions":{"get":{"summary":"List data reactions","description":"Returns Drasi data reactions for the specified project. Requires `reactions:read`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of data reactions","content":{"application/json":{"schema":{"type":"object","properties":{"reactions":{"type":"array","items":{"$ref":"#/components/schemas/Reaction"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Reactions"]},"post":{"summary":"Create a data reaction","description":"Creates a Drasi data reaction from a data template. Requires `reactions:write`. Email and HTTP are sinks. Optional `spec.model` transforms the matching query result with a spicepod model that can tool-call back to Cloud to send the configured sink.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","templateId","spec"],"properties":{"name":{"type":"string","description":"Display name. Unique, case-insensitively, among the project's live monitors and data reactions — a collision answers 409."},"description":{"type":"string"},"templateId":{"type":"string"},"spec":{"type":"object","description":"Reaction parameters. Which properties apply is decided by `templateId` — each property below names the templates that read it, and a property a template does not read is accepted and ignored rather than rejected.","required":["op","threshold"],"properties":{"op":{"type":"string","description":"Comparison operator. Required on every reaction spec.","enum":["GT","GEQ","LT","LEQ","EQ","NEQ"]},"threshold":{"type":"number","description":"Numeric threshold. Required on every reaction spec. `task_history_slow` compares it against the task duration in milliseconds; `dataset_row_match` and `dataset_query` match on the query itself, so they conventionally send 0."},"window":{"type":"string","description":"Evaluation window. Only project monitors (`/v1/projects/{projectId}/monitors`) act on it — a data reaction fires on the matching row, so the value is stored and never read.","default":"5m","enum":["1m","5m","15m","30m","1h"]},"sustainSecs":{"type":"integer","description":"How long the condition must hold before firing, in seconds. Like `window`, only project monitors act on it.","default":300,"minimum":0,"maximum":86400},"severity":{"type":"string","description":"Severity recorded on each reaction event and carried into the email or webhook notification.","default":"critical","enum":["warn","critical"]},"dataset":{"type":"string","description":"Single dataset to watch. Required by `dataset_row_match` when `conditions` is omitted, and accepted by `dataset_query` as a one-dataset spelling of `datasets`.","maxLength":128},"column":{"type":"string","description":"Column to compare. Required by `dataset_row_match` when `conditions` is omitted.","maxLength":64},"value":{"description":"Value to compare `column` against. Required by `dataset_row_match` when `conditions` is omitted.","oneOf":[{"type":"string","maxLength":256},{"type":"number"}]},"datasets":{"type":"array","description":"Datasets the reaction watches. `dataset_query` requires this or `dataset`. On `dataset_row_match` it restricts the match to these datasets, and every one listed must be used by a condition; omit it to derive the list from `conditions`.","maxItems":20,"items":{"type":"string","maxLength":128}},"conditions":{"type":"array","description":"Row-match predicates, read by `dataset_row_match`. CONTAINS, STARTS_WITH, ENDS_WITH, IS_NULL, and IS_NOT_NULL are allowed in addition to equality and ordering. IS_NULL and IS_NOT_NULL omit value.","maxItems":20,"items":{"type":"object","required":["dataset","column","op"],"properties":{"dataset":{"type":"string"},"column":{"type":"string"},"op":{"type":"string","enum":["EQ","NEQ","GT","GEQ","LT","LEQ","CONTAINS","STARTS_WITH","ENDS_WITH","IS_NULL","IS_NOT_NULL"]},"value":{"description":"Required except for IS_NULL and IS_NOT_NULL.","oneOf":[{"type":"string"},{"type":"number"}]}}}},"queryLanguage":{"type":"string","description":"Language of `query`, read by `dataset_query`. Defaults to `gql` when omitted.","default":"gql","enum":["cypher","gql"]},"query":{"type":"string","description":"Authored continuous query. Required by `dataset_query`.","maxLength":10000},"includeDetails":{"type":"boolean","description":"When true, notifications include matching row details. Off by default."},"model":{"type":"object","description":"Optional spicepod model transform (data reactions only). The model can tool-call send_email or send_webhook for the configured sink.","required":["model","instructions"],"properties":{"model":{"type":"string","description":"Spicepod model or worker name"},"instructions":{"type":"string","description":"System instructions sent with each matching query result"}}}}},"target":{"description":"Reaction sink. Omit to email the caller.","discriminator":{"propertyName":"type"},"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["email"]},"recipientUserIds":{"type":"array","items":{"type":"integer"}},"emails":{"type":"array","items":{"type":"string","format":"email"}}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["http"]},"url":{"type":"string","format":"uri"},"method":{"type":"string","enum":["POST","PUT","PATCH"]},"token":{"type":"string"}}}]}}}}}},"responses":{"201":{"description":"Reaction created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reaction"}}}},"400":{"description":"Invalid request body or project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"A live monitor or data reaction in this project already has this name"},"413":{"description":"Request body too large"},"422":{"description":"The project's deployment target does not support Drasi reactions"},"500":{"description":"Internal server error"},"502":{"description":"Failed to provision reaction"},"503":{"description":"The project's Drasi data source is starting or unavailable"}},"tags":["Reactions"]}},"/v1/projects/{projectId}/reactions/{alertId}":{"get":{"summary":"Get a data reaction","description":"Returns a single data reaction. Requires `reactions:read`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"}},{"in":"path","name":"alertId","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Reaction","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reaction"}}}},"400":{"description":"Invalid project or reaction ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or reaction not found"},"500":{"description":"Internal server error"}},"tags":["Reactions"]},"delete":{"summary":"Delete a data reaction","description":"Soft-deletes a data reaction and tears down its Drasi query. Requires `reactions:write`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"projectId","required":true,"schema":{"type":"integer"}},{"in":"path","name":"alertId","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Reaction deleted"},"400":{"description":"Invalid project or reaction ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or reaction not found"},"500":{"description":"Internal server error"},"502":{"description":"Failed to delete reaction"}},"tags":["Reactions"]}},"/v1/orgs":{"get":{"summary":"List the caller's organizations","description":"Returns every organization the authenticated caller belongs to, each with the caller's effective membership role. A caller with no memberships gets an empty array, not a 404 — clients read 404 as \"this deployment does not serve the endpoint\", which is a different state. `name` is the organization handle used in `<org>/<app>` addressing.\n","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"The organizations the caller is a member of, possibly empty","content":{"application/json":{"schema":{"type":"object","required":["orgs"],"properties":{"orgs":{"type":"array","items":{"type":"object","required":["id","name","role"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"The caller's highest-privilege role in the organization."}}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"500":{"description":"Internal server error"}},"tags":["Orgs"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]}},"/v1/members":{"get":{"summary":"List organization members","description":"Returns all members of the authenticated organization.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of organization members","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer"},"username":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"is_owner":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"}},"tags":["Members"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]},"post":{"summary":"Add a member","description":"Adds a new member to the organization with the specified roles.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username"],"properties":{"username":{"type":"string","description":"The username of the user to add"},"roles":{"type":"array","items":{"type":"string"},"description":"Roles to assign to the new member","default":["member"]}}}}}},"responses":{"201":{"description":"Member added successfully","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"integer"},"username":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, not an org admin/owner, or only owners can assign the admin or owner role"},"404":{"description":"User not found"},"409":{"description":"User is already a member of this organization"}},"tags":["Members"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]}},"/v1/members/{memberId}":{"get":{"summary":"Get a member","description":"Returns details for a specific organization member, including their roles.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"memberId","required":true,"schema":{"type":"integer"},"description":"The user ID of the member"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Member details","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"integer"},"username":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"is_owner":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Member not found"}},"tags":["Members"]},"patch":{"summary":"Update a member's roles","description":"Updates the roles assigned to a specific organization member. Organization owners cannot have their roles modified.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"memberId","required":true,"schema":{"type":"integer"},"description":"The user ID of the member"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roles"],"properties":{"roles":{"type":"array","items":{"type":"string"},"description":"New roles to assign to the member"}}}}}},"responses":{"200":{"description":"Member roles updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"integer"},"username":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, not an org admin/owner, cannot modify owner, only owners can modify/assign admin or owner roles"},"404":{"description":"Member not found"}},"tags":["Members"]},"delete":{"summary":"Remove a member","description":"Removes a member from the organization. Organization owners cannot be removed.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"memberId","required":true,"schema":{"type":"integer"},"description":"The user ID of the member"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Member removed successfully"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, not an org admin/owner, cannot remove owner, or only owners can remove admins or owners"},"404":{"description":"Member not found"}},"tags":["Members"]}},"/v1/limits":{"get":{"summary":"Get organization limits","description":"Returns the current plan limits for the authenticated organization, including resource limits when private compute is enabled.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Organization limits","content":{"application/json":{"schema":{"type":"object","properties":{"apps":{"type":"integer","description":"Maximum number of apps (-1 means unlimited)"},"replicas":{"type":"integer","description":"Maximum replicas per app (-1 means unlimited)"},"spicepods":{"type":"integer","description":"Maximum spicepods per app (-1 means unlimited)"},"users":{"type":"integer","description":"Maximum organization members (-1 means unlimited)"},"api_rps":{"type":"integer","description":"API requests per second limit (-1 means unlimited)"},"request_timeout_secs":{"type":"integer","description":"Request timeout in seconds"},"sql_query_timeout_secs":{"type":"integer","description":"SQL query timeout in seconds"},"sql_concurrent_queries":{"type":"integer","description":"Maximum concurrent SQL queries (-1 means unlimited)"},"resources":{"type":"object","nullable":true,"description":"Resource limits for private compute; `null` when private compute is not enabled","properties":{"max_cpu":{"type":"integer","description":"Maximum vCPUs per container"},"max_memory_gi":{"type":"integer","description":"Maximum memory in Gi per container"}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Organization not found"}},"tags":["Limits"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]}},"/v1/regions":{"get":{"summary":"List regions","description":"Returns the available regions where apps can be deployed.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of available regions","content":{"application/json":{"schema":{"type":"object","properties":{"regions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable region name"},"region":{"type":"string","description":"Region identifier (e.g., us-east-2)"},"provider":{"type":"string","enum":["teraswitch","aws","azure"]},"providerName":{"type":"string","enum":["Teraswitch","AWS","Azure"]},"isDefault":{"type":"boolean"},"disabled":{"type":"boolean"}}}},"default":{"type":"string","description":"The default region identifier"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"}},"tags":["Regions"],"parameters":[{"$ref":"#/components/parameters/OrgName"}]}},"/openapi.json":{"get":{"summary":"Get OpenAPI specification","description":"Returns the OpenAPI specification for the Spice Cloud Control-Plane API.","responses":{"200":{"description":"OpenAPI specification document","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Documentation"]}},"/v1/apps":{"get":{"summary":"List projects","description":"Legacy alias of `/v1/projects` — projects were previously called apps. Returns all projects belonging to the authenticated organization.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"object","properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"}},"tags":["Projects"],"parameters":[{"$ref":"#/components/parameters/OrgName"}],"deprecated":true},"post":{"summary":"Create a project","description":"Legacy alias of `/v1/projects` — projects were previously called apps. Creates a new project in the authenticated organization.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":4,"pattern":"^[a-zA-Z0-9-]+$","description":"Project name (letters, numbers, hyphens only)"},"region":{"type":"string","enum":["us-east-1","us-west-2"],"description":"Cloud region where a managed project will be deployed (e.g. `us-east-1`). Omit this, `cname`, and `cluster_name` to create an unattached standalone project whose placement is resolved when linked.\n"},"cname":{"type":"string","description":"**Deprecated.** Internal region CNAME (e.g. `us-east-1-prod-aws-data`). Use `region` instead. Accepted for backwards compatibility when `region` is omitted. Omit all region sources to create an unattached standalone project.\n"},"cluster_name":{"type":"string","description":"Name of the dedicated cluster to deploy to, as listed by `GET /v1/clusters`. Provide exactly one of `region` or `cluster_name`. When set, the region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Omit all region sources to create an unattached standalone project.\n"},"description":{"type":"string","description":"Project description"},"visibility":{"type":"string","enum":["public","private"],"default":"private"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed project deployment. A standalone project has no spicepod until linking, so configure its channel after linking."},"replicas":{"type":"integer","minimum":0,"description":"Number of replicas for a managed project. Rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container. Rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project. Rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}}}}}}},"responses":{"201":{"description":"Project created successfully. The response may include a `warning` field if initial configuration could not be applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or invalid region"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, dedicated-cluster assignment or special-org private-compute ceilings required for resource limits, or requested resources exceed dedicated-cluster capacity"},"409":{"description":"A project with this name already exists"},"429":{"description":"Too many project creation requests (rate limited)"},"500":{"description":"Project creation failed, including when the name-collision check could not be read","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Generic failure reason; the underlying cause stays in the server logs","example":"Failed to create project"}}}}}}},"tags":["Projects"],"parameters":[{"$ref":"#/components/parameters/OrgName"}],"deprecated":true}},"/v1/apps/{appId}":{"get":{"summary":"Get a project","description":"Legacy alias of `/v1/projects/{projectId}` — projects were previously called apps. Returns details for a specific project, including its configuration.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project details with configuration","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"created_at":{"type":"string","format":"date-time"},"cluster_name":{"type":"string","nullable":true,"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":"string","nullable":true,"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":"string"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":"object","additionalProperties":{"type":"string"}},"paused_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp when the project was paused; null while running"},"config":{"type":"object","properties":{"spicepod":{"type":"object"},"image_tag":{"type":"string","description":"Runtime image tag"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"nullable":true,"description":"Update channel for the runtime, or null when Spice operates this project's runtime updates."},"version":{"type":"string","description":"Semver range for the runtime version (e.g. 1.x, 2.x)"},"replicas":{"type":"integer"},"resources":{"type":"object","nullable":true,"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","nullable":true,"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number","nullable":true}}},"region":{"type":"string","nullable":true,"description":"Server-resolved region; null before a standalone instance is attached."},"storage_size_gb":{"type":"number","nullable":true},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead."}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true},"put":{"summary":"Update a project","description":"Legacy alias of `/v1/projects/{projectId}` — projects were previously called apps. Updates a project's metadata and configuration, including description, visibility, spicepod configuration, replicas, and resource limits.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"production_branch":{"type":"string"},"tags":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value tags for the project"},"spicepod":{"oneOf":[{"type":"string","description":"YAML string of the spicepod configuration"},{"type":"object","description":"JSON object of the spicepod configuration"}]},"image_tag":{"type":"string","nullable":true,"description":"Runtime image tag for a managed project; rejected for standalone projects. A non-empty value pins the project. On stable or preview, send null to clear a pin so deploys resolve from the channel catalog (stable - latest GA for the configured version range; preview - latest RC). null is rejected on nightly and internal channels."},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel for a managed runtime; rejected for standalone projects."},"version":{"type":"string","description":"Semver range for a managed runtime version (e.g. 1.x, 2.x); rejected for standalone projects."},"replicas":{"type":"integer","minimum":0,"description":"Managed runtime replica count; rejected for standalone projects."},"resources":{"type":"object","description":"Resource requests and limits for a managed project container; rejected for standalone projects.","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","description":"Executor container configuration for a managed project; rejected for standalone projects.","properties":{"replicas":{"type":"integer","minimum":0,"description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string","description":"Ephemeral storage limit in Gi (for example, 8Gi)"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"storage_size_gb":{"type":"number"}}},"region":{"type":"string","description":"Managed project region; standalone placement is resolved during attachment."},"cluster_name":{"type":"string","description":"Reassign the project to a dedicated cluster, as listed by `GET /v1/clusters`. Provide at most one of `region` or `cluster_name`. When set, region/routing are derived from the cluster; if `region` is also provided it must match the cluster's region. Rejected for standalone projects, whose placement is resolved during attachment.\n"},"storage_size_gb":{"type":"number","description":"Managed runtime storage size; rejected for standalone projects."},"storage_claim_size_gb":{"type":"number","deprecated":true,"description":"Deprecated. Use storage_size_gb instead. Rejected for standalone projects."}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectWithConfig"}}}},"400":{"description":"Invalid request body or project ID, including placement or hosted runtime fields that do not apply to standalone projects"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot mutate projects, requested replicas or resources exceed plan limits, dedicated-cluster assignment or special-org private-compute ceilings required, requested resources exceed dedicated-cluster capacity, `image_tag` was supplied outside the Enterprise plan (code: `image_tag_requires_enterprise`), or the requested runtime image is not available for the organization plan (code: `image_tag_not_allowed_for_plan`); omitting `image_tag` leaves the configured image unchanged"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"},"503":{"description":"Runtime version catalog is unavailable"}},"tags":["Projects"],"deprecated":true},"delete":{"summary":"Delete a project","description":"Legacy alias of `/v1/projects/{projectId}` — projects were previously called apps. Deletes a project and tears down its runtime resources.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to delete"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Project deleted successfully"},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot mutate projects"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true}},"/v1/apps/{appId}/pause":{"post":{"summary":"Pause a project","description":"Legacy alias of `/v1/projects/{projectId}/pause` — projects were previously called apps. Pauses the project's running spicepod. The runtime is torn down and the\nspicepod record is marked as paused. Settings and configuration are\npreserved. Resume with POST /v1/projects/{projectId}/resume. Requires\n`apps:write`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to pause"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project paused","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp when the project was paused"}}}}}},"400":{"description":"Invalid project ID, missing API key, no spicepod configured, or spicepod has been deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"Project is already paused","content":{"application/json":{"schema":{"type":"object","required":["error","paused_at"],"properties":{"error":{"type":"string"},"paused_at":{"type":"string","format":"date-time","description":"Timestamp of the existing pause"}}}}}},"500":{"description":"Internal server error"}},"tags":["Projects"],"deprecated":true}},"/v1/apps/{appId}/resume":{"post":{"summary":"Resume a paused project","description":"Legacy alias of `/v1/projects/{projectId}/resume` — projects were previously called apps. Clears the paused state on the project's spicepod and creates a new\ndeployment to bring the runtime back up. Requires `apps:write`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project to resume"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Project resumed, deployment created","content":{"application/json":{"schema":{"type":"object","required":["id","name","paused_at","deployment_id"],"properties":{"id":{"type":"integer","description":"The project ID"},"name":{"type":"string","description":"The project name"},"paused_at":{"type":"string","format":"date-time","nullable":true,"description":"Always null after a successful resume"},"deployment_id":{"type":"integer","description":"ID of the deployment created to bring the runtime back up"}}}}}},"400":{"description":"Invalid project ID, missing API key, missing project organization, no spicepod configured, spicepod has been deleted, or spicepod configuration is invalid"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"Project is not paused"},"422":{"description":"No runtime version in the catalog matches this project’s version range (`code: version_no_match`).\n"},"500":{"description":"Internal server error"},"503":{"description":"The runtime version catalog (Edge Config) is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview resume cannot resolve an image tag.\n"}},"tags":["Projects"],"deprecated":true}},"/v1/apps/{appId}/metrics":{"get":{"summary":"Get project metrics","description":"Legacy alias of `/v1/projects/{projectId}/metrics` — projects were previously called apps. Returns current resource utilization metrics (CPU, memory, disk I/O) and data ingestion metrics for a project.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"window","required":false,"schema":{"type":"string"},"description":"Prometheus-style duration (e.g. `5m`, `1h`, `30s`). When provided, counter metrics are returned as rates over the window instead of raw cumulative values. For example, `rows_ingested` returns average rows per second with a window, or total cumulative rows without one."},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Resource and ingestion metrics. Missing fields indicate no data is currently available. Individual metric queries may fail independently; partial results are returned when possible.","content":{"application/json":{"schema":{"type":"object","properties":{"ingestion":{"type":"object","description":"Aggregated data ingestion metrics","properties":{"rows_ingested":{"type":"number","description":"Total rows ingested (without window) or rows per second (with window)"},"bytes_ingested":{"type":"number","description":"Total bytes ingested (without window) or bytes per second (with window)"}}},"cluster":{"type":"object","description":"Cluster metrics for the project","properties":{"active_executors_count":{"type":"number","description":"Number of active executors registered with the scheduler"}}},"metrics":{"type":"object","description":"Resource metrics keyed by pod name","additionalProperties":{"type":"object","properties":{"cpu_usage_percent":{"type":"number","description":"CPU usage as a percentage (0-100)"},"memory_usage_bytes":{"type":"number","description":"Memory usage in bytes"},"disk_read_bytes":{"type":"number","description":"Disk bytes read per second (with window) or cumulative (without window)"},"disk_read_operations":{"type":"number","description":"Disk read operations per second (with window) or cumulative count (without window)"},"disk_write_bytes":{"type":"number","description":"Disk bytes written per second (with window) or cumulative (without window)"},"disk_write_operations":{"type":"number","description":"Disk write operations per second (with window) or cumulative count (without window)"}}}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or organization not found"},"422":{"description":"Organization is not fully configured"},"500":{"description":"Internal server error"},"503":{"description":"Metrics service unavailable"}},"tags":["Projects"],"deprecated":true}},"/v1/apps/{appId}/deployments":{"get":{"summary":"List deployments","description":"Legacy alias of `/v1/projects/{projectId}/deployments` — projects were previously called apps. Returns a list of deployments for the specified project, ordered by most recent first.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Maximum number of deployments to return"},{"in":"query","name":"status","schema":{"type":"string","enum":["queued","in_progress","succeeded","failed","created"]},"description":"Filter by deployment status"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of deployments","content":{"application/json":{"schema":{"type":"object","properties":{"deployments":{"type":"array","items":{"$ref":"#/components/schemas/Deployment"}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"}},"tags":["Deployments"],"deprecated":true},"post":{"summary":"Create a deployment","description":"Legacy alias of `/v1/projects/{projectId}/deployments` — projects were previously called apps. Creates a new deployment for the specified project using its current spicepod configuration. The runtime image is selected based on the project's update channel.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"image_tag":{"type":"string","description":"Override the runtime image tag for this deployment. Requires the Enterprise plan; omit it to deploy the image the project is configured with."},"channel":{"type":"string","enum":["stable","preview","nightly"],"description":"Update channel that determines the runtime image"},"replicas":{"type":"integer","minimum":1,"maximum":10,"description":"Override the number of replicas for this deployment"},"branch":{"type":"string","description":"Git branch name"},"commit_sha":{"type":"string","description":"Git commit SHA"},"commit_message":{"type":"string","description":"Git commit message"},"debug":{"type":"boolean","description":"Enable debug mode for this deployment"},"target":{"type":"object","description":"Optional per-deployment override. Omit (or `{ kind: cloud }`) to place the deployment where the project itself runs — Spice-managed compute, its own BYOC cluster, or its attached standalone Cloud Connect instance — which is a property of the project, not of this request. Pass `{ kind: cluster, instance_id }` to override that for this deployment and route it to a BYOC cluster connection; the cluster must be owned by the token's org, not in a terminal lifecycle state (decommissioning/archived), not paused, and the operator must hold a live connection (verified by the connectivity ping, not the Supabase lifecycle status). A standalone project cannot be overridden this way and is refused with 409.\n","properties":{"kind":{"type":"string","enum":["cloud","cluster"]},"instance_id":{"type":"string","description":"Required when kind is `cluster`."}}}}}}}},"responses":{"202":{"description":"Deployment created and queued for processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deployment"}}}},"400":{"description":"Invalid request body, project has no spicepod configuration, the spicepod has been deleted, or the spicepod is paused (resume it with POST /v1/projects/{projectId}/resume before deploying). For a standalone Cloud Connect project, also returned when the Spicepod references a `${secrets:...}` name the project does not define: the body carries `code: unknown_secret_references`, a `missing_secrets` array of `{name, path, suggestion?}`, and `secrets_url`. Nothing is dispatched and no deployment is recorded.\n"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope, viewer role cannot create deployments, `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` deploys the image the project is configured with"},"404":{"description":"Project not found — or, for a `cluster` target, the BYOC cluster connection does not exist or is not owned by the token's org.\n"},"409":{"description":"A deployment is already in progress for this project — or, for a `cluster` target, the BYOC cluster is paused, offline, or otherwise not in a deployable state. Also returned when the project's deploy target is unusable: its kind conflicts with its cluster/instance references, a `cluster` target was requested for a standalone project, a standalone project has no attached instance, or that instance has not yet enrolled the encryption key its secrets are sealed to (retrying once it is online succeeds).\n"},"413":{"description":"Request body exceeds the maximum size (1 MiB), or — for a standalone Cloud Connect project — the project's resolved secrets exceed the maximum sealed-payload size. Nothing is dispatched and no deployment is recorded.\n"},"422":{"description":"Organization is not fully configured, or no runtime version in the catalog matches this project’s version range (`code: version_no_match`).\n"},"500":{"description":"Internal server error. For a standalone Cloud Connect project, also returned with `code: deployment_not_settled` when the deployment record could not be moved out of `queued`; the project's next deployment is refused until it is cleared.\n"},"502":{"description":"Upstream service failure while provisioning the deployment, or — for a standalone Cloud Connect project — the spicepod could not be handed to the instance's gateway, or the instance rejected it.\n"},"503":{"description":"The runtime version catalog (Edge Config) is unavailable or empty (`code: version_source_unavailable`), so an unpinned stable or preview deploy cannot resolve an image tag — or catalog membership for an explicit `image_tag` override cannot be validated.\n"}},"tags":["Deployments"],"deprecated":true}},"/v1/apps/{appId}/deployments/{deploymentId}":{"get":{"summary":"Get a deployment","description":"Legacy alias of `/v1/projects/{projectId}/deployments/{deploymentId}` — projects were previously called apps. Returns a single deployment for the specified project. The body matches one item from GET /v1/projects/{projectId}/deployments. Not cacheable. Poll this path for stored status after POST create.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"deploymentId","required":true,"schema":{"type":"integer"},"description":"The ID of the deployment"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deployment"}}}},"400":{"description":"Invalid project ID or deployment ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or deployment not found"},"500":{"description":"Internal server error"}},"tags":["Deployments"],"deprecated":true}},"/v1/apps/{appId}/secrets":{"get":{"summary":"List secrets","description":"Legacy alias of `/v1/projects/{projectId}/secrets` — projects were previously called apps. Returns all secrets for the specified project. Secret values are always masked.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of secrets","content":{"application/json":{"schema":{"type":"object","properties":{"secrets":{"type":"array","items":{"$ref":"#/components/schemas/Secret"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot perform write operations"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"],"deprecated":true},"post":{"summary":"Create or update a secret","description":"Legacy alias of `/v1/projects/{projectId}/secrets` — projects were previously called apps. Creates a new secret or updates an existing secret with the same name. The secret value is encrypted at rest.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$","description":"Secret name (must start with a letter or underscore, alphanumeric and underscores only)"},"value":{"type":"string","description":"Secret value (will be encrypted at rest)"}}}}}},"responses":{"200":{"description":"Secret created or updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}}},"400":{"description":"Invalid request body or project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"],"deprecated":true}},"/v1/apps/{appId}/secrets/{secretName}":{"get":{"summary":"Get a secret","description":"Legacy alias of `/v1/projects/{projectId}/secrets/{secretName}` — projects were previously called apps. Returns a specific secret by name. The secret value is always masked.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"secretName","required":true,"schema":{"type":"string"},"description":"The name of the secret"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Secret details (value is masked)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or admin or owner role required"},"404":{"description":"Project or secret not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"],"deprecated":true},"delete":{"summary":"Delete a secret","description":"Legacy alias of `/v1/projects/{projectId}/secrets/{secretName}` — projects were previously called apps. Permanently deletes a secret by name.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"in":"path","name":"secretName","required":true,"schema":{"type":"string"},"description":"The name of the secret"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"204":{"description":"Secret deleted successfully"},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or secret not found"},"500":{"description":"Internal server error"}},"tags":["Secrets"],"deprecated":true}},"/v1/apps/{appId}/api-keys":{"get":{"summary":"Get API keys","description":"Legacy alias of `/v1/projects/{projectId}/api-keys` — projects were previously called apps. Returns the API keys for a specific project. API keys are used to authenticate requests to the Spice.ai runtime.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"API keys for the project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeys"}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope or viewer role cannot regenerate API keys"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["API Keys"],"deprecated":true},"post":{"summary":"Regenerate an API key","description":"Legacy alias of `/v1/projects/{projectId}/api-keys` — projects were previously called apps. Regenerates an API key for the specified project. This invalidates the previous key. Use key_number to specify which key to regenerate (0 for both, 1 for primary, 2 for secondary).","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"key_number":{"type":"integer","enum":[0,1,2],"default":1,"description":"Which API key to regenerate (0 = both, 1 = primary, 2 = secondary)"}}}}}},"responses":{"200":{"description":"API key regenerated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeysRegenerated"}}}},"400":{"description":"Invalid request body or project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["API Keys"],"deprecated":true}},"/v1/apps/{appId}/reactions":{"get":{"summary":"List data reactions","description":"Legacy alias of `/v1/projects/{projectId}/reactions` — projects were previously called apps. Returns Drasi data reactions for the specified project. Requires `reactions:read`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"List of data reactions","content":{"application/json":{"schema":{"type":"object","properties":{"reactions":{"type":"array","items":{"$ref":"#/components/schemas/Reaction"}}}}}}},"400":{"description":"Invalid project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"500":{"description":"Internal server error"}},"tags":["Reactions"],"deprecated":true},"post":{"summary":"Create a data reaction","description":"Legacy alias of `/v1/projects/{projectId}/reactions` — projects were previously called apps. Creates a Drasi data reaction from a data template. Requires `reactions:write`. Email and HTTP are sinks. Optional `spec.model` transforms the matching query result with a spicepod model that can tool-call back to Cloud to send the configured sink.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"},"description":"The ID of the project"},{"$ref":"#/components/parameters/OrgName"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","templateId","spec"],"properties":{"name":{"type":"string","description":"Display name. Unique, case-insensitively, among the project's live monitors and data reactions — a collision answers 409."},"description":{"type":"string"},"templateId":{"type":"string"},"spec":{"type":"object","description":"Reaction parameters. Which properties apply is decided by `templateId` — each property below names the templates that read it, and a property a template does not read is accepted and ignored rather than rejected.","required":["op","threshold"],"properties":{"op":{"type":"string","description":"Comparison operator. Required on every reaction spec.","enum":["GT","GEQ","LT","LEQ","EQ","NEQ"]},"threshold":{"type":"number","description":"Numeric threshold. Required on every reaction spec. `task_history_slow` compares it against the task duration in milliseconds; `dataset_row_match` and `dataset_query` match on the query itself, so they conventionally send 0."},"window":{"type":"string","description":"Evaluation window. Only project monitors (`/v1/projects/{projectId}/monitors`) act on it — a data reaction fires on the matching row, so the value is stored and never read.","default":"5m","enum":["1m","5m","15m","30m","1h"]},"sustainSecs":{"type":"integer","description":"How long the condition must hold before firing, in seconds. Like `window`, only project monitors act on it.","default":300,"minimum":0,"maximum":86400},"severity":{"type":"string","description":"Severity recorded on each reaction event and carried into the email or webhook notification.","default":"critical","enum":["warn","critical"]},"dataset":{"type":"string","description":"Single dataset to watch. Required by `dataset_row_match` when `conditions` is omitted, and accepted by `dataset_query` as a one-dataset spelling of `datasets`.","maxLength":128},"column":{"type":"string","description":"Column to compare. Required by `dataset_row_match` when `conditions` is omitted.","maxLength":64},"value":{"description":"Value to compare `column` against. Required by `dataset_row_match` when `conditions` is omitted.","oneOf":[{"type":"string","maxLength":256},{"type":"number"}]},"datasets":{"type":"array","description":"Datasets the reaction watches. `dataset_query` requires this or `dataset`. On `dataset_row_match` it restricts the match to these datasets, and every one listed must be used by a condition; omit it to derive the list from `conditions`.","maxItems":20,"items":{"type":"string","maxLength":128}},"conditions":{"type":"array","description":"Row-match predicates, read by `dataset_row_match`. CONTAINS, STARTS_WITH, ENDS_WITH, IS_NULL, and IS_NOT_NULL are allowed in addition to equality and ordering. IS_NULL and IS_NOT_NULL omit value.","maxItems":20,"items":{"type":"object","required":["dataset","column","op"],"properties":{"dataset":{"type":"string"},"column":{"type":"string"},"op":{"type":"string","enum":["EQ","NEQ","GT","GEQ","LT","LEQ","CONTAINS","STARTS_WITH","ENDS_WITH","IS_NULL","IS_NOT_NULL"]},"value":{"description":"Required except for IS_NULL and IS_NOT_NULL.","oneOf":[{"type":"string"},{"type":"number"}]}}}},"queryLanguage":{"type":"string","description":"Language of `query`, read by `dataset_query`. Defaults to `gql` when omitted.","default":"gql","enum":["cypher","gql"]},"query":{"type":"string","description":"Authored continuous query. Required by `dataset_query`.","maxLength":10000},"includeDetails":{"type":"boolean","description":"When true, notifications include matching row details. Off by default."},"model":{"type":"object","description":"Optional spicepod model transform (data reactions only). The model can tool-call send_email or send_webhook for the configured sink.","required":["model","instructions"],"properties":{"model":{"type":"string","description":"Spicepod model or worker name"},"instructions":{"type":"string","description":"System instructions sent with each matching query result"}}}}},"target":{"description":"Reaction sink. Omit to email the caller.","discriminator":{"propertyName":"type"},"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["email"]},"recipientUserIds":{"type":"array","items":{"type":"integer"}},"emails":{"type":"array","items":{"type":"string","format":"email"}}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["http"]},"url":{"type":"string","format":"uri"},"method":{"type":"string","enum":["POST","PUT","PATCH"]},"token":{"type":"string"}}}]}}}}}},"responses":{"201":{"description":"Reaction created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reaction"}}}},"400":{"description":"Invalid request body or project ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project not found"},"409":{"description":"A live monitor or data reaction in this project already has this name"},"413":{"description":"Request body too large"},"422":{"description":"The project's deployment target does not support Drasi reactions"},"500":{"description":"Internal server error"},"502":{"description":"Failed to provision reaction"},"503":{"description":"The project's Drasi data source is starting or unavailable"}},"tags":["Reactions"],"deprecated":true}},"/v1/apps/{appId}/reactions/{alertId}":{"get":{"summary":"Get a data reaction","description":"Legacy alias of `/v1/projects/{projectId}/reactions/{alertId}` — projects were previously called apps. Returns a single data reaction. Requires `reactions:read`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"}},{"in":"path","name":"alertId","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Reaction","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reaction"}}}},"400":{"description":"Invalid project or reaction ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or reaction not found"},"500":{"description":"Internal server error"}},"tags":["Reactions"],"deprecated":true},"delete":{"summary":"Delete a data reaction","description":"Legacy alias of `/v1/projects/{projectId}/reactions/{alertId}` — projects were previously called apps. Soft-deletes a data reaction and tears down its Drasi query. Requires `reactions:write`.","security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"integer"}},{"in":"path","name":"alertId","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/OrgName"}],"responses":{"200":{"description":"Reaction deleted"},"400":{"description":"Invalid project or reaction ID"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient scope"},"404":{"description":"Project or reaction not found"},"500":{"description":"Internal server error"},"502":{"description":"Failed to delete reaction"}},"tags":["Reactions"],"deprecated":true}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"OAuth 2.0 access token obtained from the Spice.ai authentication service"}},"parameters":{"OrgName":{"in":"header","name":"X-Org-Name","required":false,"description":"The organization to act on, by handle. Omit it to act on the organization the credential is bound to; sending it blank is a 400, not the same as omitting it. A user credential may name any organization its owner belongs to; a machine credential is pinned to its own.\n","schema":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,255}$"}}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string","nullable":true,"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":"string","nullable":true,"description":"Region identifier"},"cluster_name":{"type":"string","nullable":true,"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":"string","nullable":true,"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"tags":{"type":"object","additionalProperties":{"type":"string"},"nullable":true,"description":"Key-value tags for the project"}}},"ProjectWithConfig":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the project"},"name":{"type":"string","description":"Name of the project"},"kind":{"type":"string","enum":["managed","standalone","byoc"],"description":"Resolved project hosting model"},"description":{"type":"string","nullable":true,"description":"Description of the project"},"visibility":{"type":"string","enum":["public","private"],"description":"Visibility setting for the project"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the project was created"},"cname":{"type":"string","nullable":true,"description":"Region identifier"},"cluster_name":{"type":"string","nullable":true,"description":"Resolved dedicated cluster name; null for projects not assigned to a dedicated cluster."},"endpoint":{"type":"string","nullable":true,"description":"The data-plane endpoint the project connects to, or null before a standalone instance is attached."},"production_branch":{"type":"string","nullable":true,"description":"Production branch for the project"},"api_key":{"type":"string","deprecated":true,"description":"Deprecated. Use the API Keys endpoints instead."},"tags":{"type":"object","additionalProperties":{"type":"string"},"nullable":true,"description":"Key-value tags for the project"},"paused_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp when the project was paused; null while running"},"warning":{"type":"string","nullable":true,"description":"Present when the project was created but initial configuration could not be applied"},"config":{"type":"object","nullable":true,"description":"Runtime configuration; null for an unattached standalone project.","properties":{"spicepod":{"type":"object","nullable":true,"description":"Spicepod configuration"},"registry":{"type":"string","nullable":true,"description":"Container registry for the runtime image"},"image_tag":{"type":"string","nullable":true,"description":"Runtime image tag"},"update_channel":{"type":"string","enum":["stable","preview","nightly"],"nullable":true,"description":"Update channel for the runtime"},"replicas":{"type":"integer","description":"Number of replicas"},"resources":{"type":"object","nullable":true,"description":"Resource requests and limits for the project container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}},"executor":{"type":"object","nullable":true,"description":"Executor container configuration","properties":{"replicas":{"type":"integer","description":"Number of executor replicas"},"resources":{"type":"object","description":"Resource requests and limits for the executor container","properties":{"limits":{"type":"object","properties":{"cpu":{"type":"string","description":"Whole-number vCPU limit, or '-' for no CPU limit"},"memory":{"type":"string","description":"Memory limit in Gi (for example, 16Gi)"},"ephemeral-storage":{"type":"string"}}},"requests":{"type":"object","properties":{"cpu":{"type":"string","description":"CPU request. Minimum 100 millicores (for example, 100m or 0.1)."},"memory":{"type":"string","description":"Memory request. Minimum 192Mi."}}}}}}},"region":{"type":"string","nullable":true,"description":"Cloud region code (e.g., us-east-1)"},"node_group":{"type":"string","nullable":true,"description":"Node group for scheduling the project runtime"},"storage_claim_size_gb":{"type":"number","nullable":true,"description":"Storage claim size in GB"}}}}},"Secret":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the secret"},"name":{"type":"string","description":"Name of the secret"},"value":{"type":"string","description":"Always masked with asterisks"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the secret was created"},"updated_at":{"type":"string","format":"date-time","description":"Timestamp when the secret was last updated"}}},"Deployment":{"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier for the deployment"},"status":{"type":"string","enum":["queued","in_progress","succeeded","failed","created"],"description":"Current status of the deployment"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the deployment was created"},"started_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp when the deployment started"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp when the deployment was last updated"},"image_tag":{"type":"string","nullable":true,"description":"Runtime image tag used for this deployment"},"replicas":{"type":"integer","description":"Number of replicas"},"branch":{"type":"string","nullable":true,"description":"Git branch name"},"commit_sha":{"type":"string","nullable":true,"description":"Git commit SHA"},"commit_message":{"type":"string","nullable":true,"description":"Git commit message"},"error_message":{"type":"string","nullable":true,"description":"Error message if the deployment failed"},"creation_source":{"type":"string","nullable":true,"description":"Source that triggered the deployment"},"created_by":{"type":"integer","nullable":true,"description":"User ID who created the deployment"}}},"ApiKeys":{"type":"object","properties":{"api_key":{"type":"string","nullable":true,"description":"Primary API key"},"api_key_2":{"type":"string","nullable":true,"description":"Secondary API key (for key rotation)"}}},"ApiKeysRegenerated":{"type":"object","properties":{"api_key":{"type":"string","nullable":true,"description":"Primary API key"},"api_key_2":{"type":"string","nullable":true,"description":"Secondary API key"},"regenerated_key":{"type":"integer","enum":[0,1,2],"description":"Which key was regenerated (0 = both, 1 = primary, 2 = secondary)"}}},"Reaction":{"type":"object","description":"A data reaction, as returned by the list, get and create operations. Every property below is always present; a nullable one carries null rather than being omitted. Fields not listed here are internal to the platform and are not part of the contract.","required":["id","org_id","app_id","created_by","name","description","kind","status","template_id","spec","target","recipient_user_ids","last_fired_at","last_resolved_at","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the reaction. This is the value the delete operation takes."},"org_id":{"type":"integer","description":"Organization that owns the project"},"app_id":{"type":"integer","description":"Project the reaction belongs to"},"created_by":{"type":"integer","nullable":true,"description":"User who created the reaction, when it was created by a user credential"},"name":{"type":"string","description":"Display name, unique case-insensitively among the project's live monitors and data reactions"},"description":{"type":"string","nullable":true},"kind":{"type":"string","enum":["data"],"description":"Discriminates a data reaction from a project monitor. The reactions endpoints only ever return `data`."},"status":{"type":"string","description":"Whether the reaction is evaluating","enum":["active","disabled"]},"template_id":{"type":"string","description":"Template the reaction was created from"},"spec":{"type":"object","description":"Reaction parameters, normalized for the response: a reaction whose model transform is still held on `target` is returned with that transform copied into `spec.model`. Which properties are meaningful is decided by `template_id` — see the create operation's request body for the properties each template reads."},"target":{"description":"The reaction sink. An HTTP target's `token` is write-only and is never returned.","discriminator":{"propertyName":"type"},"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["email"]},"recipientUserIds":{"type":"array","items":{"type":"integer"}},"emails":{"type":"array","items":{"type":"string","format":"email"}}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["http"]},"url":{"type":"string","format":"uri"},"method":{"type":"string","enum":["POST","PUT","PATCH"]}}}]},"recipient_user_ids":{"type":"array","description":"Organization members emailed when the reaction fires. Always empty for `http` targets, and for `email` targets addressed only by `emails`. When a created `email` target named no recipients at all, the API stores the creating user.","items":{"type":"integer"}},"last_fired_at":{"type":"string","format":"date-time","nullable":true,"description":"When the reaction last fired, or null if it never has"},"last_resolved_at":{"type":"string","format":"date-time","nullable":true,"description":"When the reaction last resolved, or null if it never has"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}},"tags":[{"name":"Catalogs","description":"Manage managed Iceberg REST catalogs (control plane)"},{"name":"Health","description":"API health and status"},{"name":"Projects","description":"Manage projects and their configurations"},{"name":"Deployments","description":"Create and monitor project deployments"},{"name":"Secrets","description":"Manage encrypted project secrets"},{"name":"API Keys","description":"Manage runtime API keys for projects"},{"name":"Members","description":"Manage organization members and roles"},{"name":"Regions","description":"Available deployment regions"},{"name":"Limits","description":"Organization plan limits"},{"name":"Documentation","description":"API specification and documentation"}]}