From c728f26778ad4340a2a059d85812e6ebbab6027c Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 24 Feb 2026 12:07:35 +0100 Subject: [PATCH] fix: update default auth URL from dash.cnap.tech to cnap.tech The dashboard and landing page are now served from cnap.tech (no longer dash.cnap.tech). Update DefaultAuthURL, help text, and OpenAPI spec to reflect the domain migration. --- internal/api/openapi.json | 2 +- internal/cmd/auth/auth.go | 2 +- internal/config/config.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/openapi.json b/internal/api/openapi.json index dbc3fca..2102075 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"CNAP API","version":"1.0.0","description":"Public API for managing CNAP workspaces, clusters, templates, products, and deployments.\n\nAuthenticate with a Personal Access Token via the `Authorization: Bearer cnap_pat_...` header.\n\nWorkspace-scoped endpoints require the `X-Workspace-Id` header."},"servers":[{"url":"https://api.cnap.tech","description":"Production"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Personal Access Token (cnap_pat_...) or OAuth2 JWT. Create tokens at https://dash.cnap.tech/settings/tokens"}},"schemas":{"ApiTokenList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiToken"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"ApiToken":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"My CLI token"},"prefix":{"type":"string","example":"cnap_pat_a3b2"},"created_at":{"type":"number","description":"Unix timestamp (seconds)","example":1708000000},"last_used_at":{"type":"number","nullable":true,"description":"Unix timestamp (seconds) of last use"},"expires_at":{"type":"number","nullable":true,"description":"Unix timestamp (seconds), null if never"}},"required":["id","name","prefix","created_at","last_used_at","expires_at"]},"Pagination":{"type":"object","properties":{"cursor":{"type":"string","nullable":true,"description":"Cursor for next page, null if no more"},"has_more":{"type":"boolean"}},"required":["cursor","has_more"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string","example":"Resource not found"},"param":{"type":"string","description":"The request field that caused the error","example":"name"},"suggestion":{"type":"string","example":"Run `cnap clusters list` to see available clusters"},"details":{"nullable":true}},"required":["code","message"]}},"required":["error"]},"CreatedToken":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"token":{"type":"string","description":"The full token. This is shown only once — store it securely.","example":"cnap_pat_a3b2c4d5e6f7g8h9i0j1k2l3m4n5o6p7"}},"required":["id","name","token"]},"WorkspaceList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Workspace":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"My Workspace"},"icon":{"type":"string","nullable":true,"example":null},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","icon","created_at"]},"ClusterList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Cluster"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Cluster":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"production"},"workspace_id":{"type":"string","example":"j572abc123def456"},"region_id":{"type":"string","example":"j572abc123def456"},"kaas":{"$ref":"#/components/schemas/KaasInfo"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","region_id","kaas","created_at"]},"KaasInfo":{"type":"object","nullable":true,"properties":{"version":{"type":"string","example":"v1.30"},"status":{"type":"string","enum":["PROVISIONING","RUNNING","RECONCILING","DELETING","ERROR","DEGRADED"],"example":"RUNNING"},"status_message":{"type":"string","nullable":true,"example":null}},"required":["version","status","status_message"],"description":"Present if cluster is KaaS-managed"},"TemplateList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Template"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Template":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"PostgreSQL HA"},"workspace_id":{"type":"string","example":"j572abc123def456"},"registry_proxy_mode":{"type":"string","nullable":true,"enum":["auto","always","never",null],"example":"auto"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","registry_proxy_mode","created_at"]},"TemplateDetail":{"allOf":[{"$ref":"#/components/schemas/Template"},{"type":"object","properties":{"helm_sources":{"type":"array","items":{"$ref":"#/components/schemas/HelmSource"}}},"required":["helm_sources"]}]},"HelmSource":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"chart":{"$ref":"#/components/schemas/HelmSourceChart"},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["id","chart"]},"HelmSourceChart":{"type":"object","properties":{"repo_url":{"type":"string","example":"https://charts.bitnami.com/bitnami"},"chart":{"type":"string","example":"postgresql"},"target_revision":{"type":"string","example":"15.5.0"},"path":{"type":"string","example":"charts/my-chart"}},"required":["repo_url","target_revision"]},"ProductList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Product":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"PostgreSQL Managed"},"workspace_id":{"type":"string","example":"j572abc123def456"},"template_id":{"type":"string","example":"j572abc123def456"},"settings":{"type":"object","nullable":true,"properties":{"custom_image":{"type":"string"},"custom_description":{"type":"string"},"show_sources":{"type":"boolean"}}},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","template_id","settings","created_at"]},"InstallList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Install"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Install":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","nullable":true,"example":"my-postgres"},"workspace_id":{"type":"string","example":"j572abc123def456"},"product_id":{"type":"string","nullable":true,"description":"Set for product-based installs"},"template_id":{"type":"string","nullable":true,"description":"Template driving this install"},"cluster_id":{"type":"string","example":"j572abc123def456"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","product_id","template_id","cluster_id","created_at"]},"Pod":{"type":"object","properties":{"name":{"type":"string","example":"postgres-0"},"containers":{"type":"array","items":{"type":"string"},"example":["postgresql"]}},"required":["name","containers"]},"RegionList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Region"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Region":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"us-east-1"},"icon":{"type":"string","nullable":true,"description":"Icon URL"},"workspace_id":{"type":"string","example":"j572abc123def456"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","icon","workspace_id","created_at"]},"RegistryCredentialList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RegistryCredential"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"RegistryCredential":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"GitHub Container Registry"},"registry_url":{"type":"string","example":"ghcr.io"},"type":{"type":"string","enum":["basic","token","oauth"],"description":"Auth type"},"is_active":{"type":"boolean","description":"Whether the credential is active"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"},"last_used_at":{"type":"number","nullable":true,"description":"Unix timestamp (seconds)"}},"required":["id","name","registry_url","type","is_active","created_at","last_used_at"]}},"parameters":{}},"paths":{"/v1/user/tokens":{"get":{"tags":["Auth"],"summary":"List personal access tokens","description":"Returns metadata for all tokens. Full token values are never shown after creation.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of tokens","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiTokenList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Auth"],"summary":"Create a personal access token","description":"Creates a new PAT. The full token is returned in the response and never shown again.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the token","example":"My CLI token"},"expires_at":{"type":"integer","minimum":0,"exclusiveMinimum":true,"description":"Unix timestamp (seconds) when the token expires. Omit for no expiry.","example":1742169600}},"required":["name"]}}}},"responses":{"201":{"description":"Token created. The `token` field is shown only once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedToken"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/user/tokens/{id}":{"delete":{"tags":["Auth"],"summary":"Revoke a personal access token","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Token ID"},"required":true,"description":"Token ID","name":"id","in":"path"}],"responses":{"204":{"description":"Token revoked"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces":{"get":{"tags":["Workspaces"],"summary":"List workspaces","description":"Lists all workspaces the authenticated user belongs to. No X-Workspace-Id needed.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of workspaces","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{id}":{"get":{"tags":["Workspaces"],"summary":"Get workspace details","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Workspace ID"},"required":true,"description":"Workspace ID","name":"id","in":"path"}],"responses":{"200":{"description":"Workspace details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/clusters":{"get":{"tags":["Clusters"],"summary":"List clusters in workspace","description":"Lists all clusters in the workspace specified by the X-Workspace-Id header.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of clusters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClusterList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/clusters/{id}":{"get":{"tags":["Clusters"],"summary":"Get cluster details","description":"Returns detailed information about a cluster, including KaaS status if applicable.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"responses":{"200":{"description":"Cluster details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cluster"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Clusters"],"summary":"Update cluster","description":"Update cluster name or region. The region must be in the same workspace.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"staging"},"region_id":{"type":"string","description":"Region ID"}}}}}},"responses":{"200":{"description":"Updated cluster","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cluster"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Clusters"],"summary":"Delete cluster","description":"Deletes a cluster and its configuration. KaaS-managed clusters are deprovisioned. Fails if the cluster has active installations.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"responses":{"204":{"description":"Cluster deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cluster has active installations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/clusters/{id}/kubeconfig":{"get":{"tags":["Clusters"],"summary":"Get cluster kubeconfig","description":"Returns the admin kubeconfig for a KaaS-managed cluster. The cluster must be in RUNNING status.\n\nSupports content negotiation via the `Accept` header:\n- `application/json` — returns JSON (default for API clients)\n- `application/yaml` — returns YAML (default for kubectl/CLI)","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"responses":{"200":{"description":"Admin kubeconfig","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"nullable":true},"description":"Kubeconfig as JSON"}},"application/yaml":{"schema":{"type":"string"}}}},"400":{"description":"Cluster is not in RUNNING status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found or not KaaS-managed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates":{"get":{"tags":["Templates"],"summary":"List templates in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Templates"],"summary":"Create template","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"PostgreSQL HA"},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","properties":{"artifact_hub_helm_package":{"type":"object","additionalProperties":{"nullable":true}},"image":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048},"tag":{"type":"string","minLength":1,"maxLength":200},"github":{"type":"object","properties":{"workflow_run_id":{"type":"string","minLength":1,"maxLength":100},"repository":{"type":"object","properties":{"id":{"type":"number"},"html_url":{"type":"string","minLength":1,"maxLength":2048},"name":{"type":"string","minLength":1,"maxLength":100},"full_name":{"type":"string","minLength":1,"maxLength":200},"owner":{"type":"object","properties":{"login":{"type":"string","minLength":1,"maxLength":100},"id":{"type":"number"},"type":{"type":"string","minLength":1,"maxLength":50},"avatar_url":{"type":"string","minLength":1,"maxLength":2048},"html_url":{"type":"string","minLength":1,"maxLength":2048}},"required":["login","id","type","avatar_url","html_url"]}},"required":["id","html_url","name","full_name","owner"]}}}},"required":["url","tag"]},"auto_deploy":{"type":"boolean"}}}},"required":["chart"]},"minItems":1},"registry_proxy_mode":{"type":"string","enum":["auto","always","never"]}},"required":["name","sources"]}}}},"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"type":"object","properties":{"template_id":{"type":"string"}},"required":["template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates/{id}":{"get":{"tags":["Templates"],"summary":"Get template details","description":"Returns template with its helm sources.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Template ID"},"required":true,"description":"Template ID","name":"id","in":"path"}],"responses":{"200":{"description":"Template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDetail"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Templates"],"summary":"Update template","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Template ID"},"required":true,"description":"Template ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","properties":{"artifact_hub_helm_package":{"type":"object","additionalProperties":{"nullable":true}},"image":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048},"tag":{"type":"string","minLength":1,"maxLength":200},"github":{"type":"object","properties":{"workflow_run_id":{"type":"string","minLength":1,"maxLength":100},"repository":{"type":"object","properties":{"id":{"type":"number"},"html_url":{"type":"string","minLength":1,"maxLength":2048},"name":{"type":"string","minLength":1,"maxLength":100},"full_name":{"type":"string","minLength":1,"maxLength":200},"owner":{"type":"object","properties":{"login":{"type":"string","minLength":1,"maxLength":100},"id":{"type":"number"},"type":{"type":"string","minLength":1,"maxLength":50},"avatar_url":{"type":"string","minLength":1,"maxLength":2048},"html_url":{"type":"string","minLength":1,"maxLength":2048}},"required":["login","id","type","avatar_url","html_url"]}},"required":["id","html_url","name","full_name","owner"]}}}},"required":["url","tag"]},"auto_deploy":{"type":"boolean"}}}},"required":["chart"]},"minItems":1},"registry_proxy_mode":{"type":"string","enum":["auto","always","never"]}}}}}},"responses":{"200":{"description":"Template updated","content":{"application/json":{"schema":{"type":"object","properties":{"template_id":{"type":"string"}},"required":["template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Templates"],"summary":"Delete template","description":"Fails if referenced by products or installs.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Template ID"},"required":true,"description":"Template ID","name":"id","in":"path"}],"responses":{"204":{"description":"Template deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Template is referenced by products or installs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/products":{"get":{"tags":["Products"],"summary":"List products in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Products"],"summary":"Create product","description":"Creates a product with helm sources and cluster associations. Triggers async chart generation.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":32,"example":"PostgreSQL Managed"},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["chart"]},"minItems":1},"cluster_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Cluster IDs to deploy to"},"stripe_price_ids":{"type":"array","items":{"type":"string"}},"settings":{"type":"object","properties":{"custom_image":{"type":"string","minLength":1,"maxLength":2048},"custom_description":{"type":"string","minLength":1,"maxLength":500},"show_sources":{"type":"boolean"}}}},"required":["name","sources","cluster_ids"]}}}},"responses":{"201":{"description":"Product created","content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string"},"template_id":{"type":"string"}},"required":["product_id","template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/products/{id}":{"get":{"tags":["Products"],"summary":"Get product details","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Product ID"},"required":true,"description":"Product ID","name":"id","in":"path"}],"responses":{"200":{"description":"Product details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Products"],"summary":"Update product","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Product ID"},"required":true,"description":"Product ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":32,"example":"PostgreSQL Managed"},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["chart"]},"minItems":1},"cluster_ids":{"type":"array","items":{"type":"string"},"minItems":1},"stripe_price_ids":{"type":"array","items":{"type":"string"}},"settings":{"type":"object","properties":{"custom_image":{"type":"string","minLength":1,"maxLength":2048},"custom_description":{"type":"string","minLength":1,"maxLength":500},"show_sources":{"type":"boolean"}}}},"required":["name","sources","cluster_ids"]}}}},"responses":{"200":{"description":"Product updated","content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string"},"template_id":{"type":"string"}},"required":["product_id","template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Products"],"summary":"Delete product","description":"Fails if the product has active installs.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Product ID"},"required":true,"description":"Product ID","name":"id","in":"path"}],"responses":{"204":{"description":"Product deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Product has active installs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs":{"get":{"tags":["Installs"],"summary":"List installs in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of installs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstallList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Installs"],"summary":"Create product install","description":"Deploys a product to a region. Pass an `Idempotency-Key` header to safely retry on timeout.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","example":"deploy-prod-2024-03-15"},"required":false,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","name":"idempotency-key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","minLength":1,"description":"Product ID"},"region_id":{"type":"string","minLength":1,"description":"Region ID"},"overrides":{"type":"array","items":{"type":"object","properties":{"template_helm_source_id":{"type":"string","minLength":1,"description":"Helm source ID to override"},"values":{"type":"object","additionalProperties":{"nullable":true},"description":"Helm values"}},"required":["template_helm_source_id","values"]},"description":"Initial value overrides per helm source"}},"required":["product_id","region_id"]}}}},"responses":{"202":{"description":"Install workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}":{"get":{"tags":["Installs"],"summary":"Get install details","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"responses":{"200":{"description":"Install details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Install"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Installs"],"summary":"Delete install","description":"Triggers async deletion of the install and its resources.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"responses":{"202":{"description":"Deletion started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/pods":{"get":{"tags":["Installs"],"summary":"List pods for install","description":"Returns pods and their containers for the install.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"responses":{"200":{"description":"List of pods","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Pod"}}},"required":["data"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/standalone":{"post":{"tags":["Installs"],"summary":"Create standalone install","description":"Deploys custom helm sources directly to clusters without a product. Pass an `Idempotency-Key` header to safely retry on timeout.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","example":"deploy-prod-2024-03-15"},"required":false,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","name":"idempotency-key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"my-redis"},"cluster_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Cluster IDs to deploy to"},"helm_sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["chart"]},"minItems":1}},"required":["name","cluster_ids","helm_sources"]}}}},"responses":{"202":{"description":"Install workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/values":{"patch":{"tags":["Installs"],"summary":"Update install template values","description":"Updates template helm source values and regenerates the chart. Use this for standalone installs or to change the base values of a product install.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"array","items":{"type":"object","properties":{"template_helm_source_id":{"type":"string","minLength":1,"description":"Helm source ID"},"values":{"type":"object","additionalProperties":{"nullable":true},"description":"Helm values"}},"required":["template_helm_source_id","values"]},"minItems":1}},"required":["updates"]}}}},"responses":{"202":{"description":"Update workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/overrides":{"patch":{"tags":["Installs"],"summary":"Update install value overrides","description":"Applies per-install value overrides on top of the product base values. Does not regenerate the chart.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"array","items":{"type":"object","properties":{"template_helm_source_id":{"type":"string","minLength":1,"description":"Helm source ID"},"values":{"type":"object","additionalProperties":{"nullable":true},"description":"Override values"}},"required":["template_helm_source_id","values"]},"minItems":1}},"required":["updates"]}}}},"responses":{"202":{"description":"Update workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/logs":{"get":{"tags":["Installs"],"summary":"Stream install logs","description":"Streams logs from the install pods via Server-Sent Events. Use the `Accept: text/event-stream` header.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":253,"description":"Pod name (all pods if omitted)"},"required":false,"description":"Pod name (all pods if omitted)","name":"pod","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":253,"description":"Container name"},"required":false,"description":"Container name","name":"container","in":"query"},{"schema":{"type":"boolean","nullable":true,"default":true,"description":"Follow log output"},"required":false,"description":"Follow log output","name":"follow","in":"query"},{"schema":{"type":"integer","nullable":true,"description":"Lines to tail"},"required":false,"description":"Lines to tail","name":"tail","in":"query"},{"schema":{"type":"integer","nullable":true,"default":0,"description":"Only return logs newer than this many seconds"},"required":false,"description":"Only return logs newer than this many seconds","name":"since_seconds","in":"query"}],"responses":{"200":{"description":"Log stream (text/event-stream)"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/regions":{"get":{"tags":["Regions"],"summary":"List regions in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of regions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Regions"],"summary":"Create region","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"us-east-1"},"icon":{"type":"string","maxLength":256,"format":"uri","description":"Icon URL"}},"required":["name"]}}}},"responses":{"201":{"description":"Region created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Region"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/registry/credentials":{"get":{"tags":["Registry"],"summary":"List registry credentials","description":"Returns credentials for the workspace. Credential secrets are never exposed.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistryCredentialList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Registry"],"summary":"Create registry credential","description":"Adds a registry credential to the workspace for OCI proxy authentication.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"GitHub Container Registry"},"registry_url":{"type":"string","minLength":1,"maxLength":2048,"example":"ghcr.io"},"type":{"type":"string","enum":["basic","token","oauth"],"description":"Auth type"},"credentials":{"type":"object","properties":{"username":{"type":"string","minLength":1,"maxLength":200},"password":{"type":"string","minLength":1,"maxLength":500},"token":{"type":"string","minLength":1,"maxLength":2000},"client_id":{"type":"string","minLength":1,"maxLength":200},"client_secret":{"type":"string","minLength":1,"maxLength":500},"token_url":{"type":"string","minLength":1,"maxLength":2048}},"description":"Auth credentials (type-dependent)"}},"required":["name","registry_url","type","credentials"]}}}},"responses":{"201":{"description":"Credential created"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/registry/credentials/{id}":{"delete":{"tags":["Registry"],"summary":"Delete registry credential","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Credential ID"},"required":true,"description":"Credential ID","name":"id","in":"path"}],"responses":{"204":{"description":"Credential deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Credential not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"CNAP API","version":"1.0.0","description":"Public API for managing CNAP workspaces, clusters, templates, products, and deployments.\n\nAuthenticate with a Personal Access Token via the `Authorization: Bearer cnap_pat_...` header.\n\nWorkspace-scoped endpoints require the `X-Workspace-Id` header."},"servers":[{"url":"https://api.cnap.tech","description":"Production"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Personal Access Token (cnap_pat_...) or OAuth2 JWT. Create tokens at https://cnap.tech/settings/tokens"}},"schemas":{"ApiTokenList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiToken"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"ApiToken":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"My CLI token"},"prefix":{"type":"string","example":"cnap_pat_a3b2"},"created_at":{"type":"number","description":"Unix timestamp (seconds)","example":1708000000},"last_used_at":{"type":"number","nullable":true,"description":"Unix timestamp (seconds) of last use"},"expires_at":{"type":"number","nullable":true,"description":"Unix timestamp (seconds), null if never"}},"required":["id","name","prefix","created_at","last_used_at","expires_at"]},"Pagination":{"type":"object","properties":{"cursor":{"type":"string","nullable":true,"description":"Cursor for next page, null if no more"},"has_more":{"type":"boolean"}},"required":["cursor","has_more"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string","example":"Resource not found"},"param":{"type":"string","description":"The request field that caused the error","example":"name"},"suggestion":{"type":"string","example":"Run `cnap clusters list` to see available clusters"},"details":{"nullable":true}},"required":["code","message"]}},"required":["error"]},"CreatedToken":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"token":{"type":"string","description":"The full token. This is shown only once — store it securely.","example":"cnap_pat_a3b2c4d5e6f7g8h9i0j1k2l3m4n5o6p7"}},"required":["id","name","token"]},"WorkspaceList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Workspace":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"My Workspace"},"icon":{"type":"string","nullable":true,"example":null},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","icon","created_at"]},"ClusterList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Cluster"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Cluster":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"production"},"workspace_id":{"type":"string","example":"j572abc123def456"},"region_id":{"type":"string","example":"j572abc123def456"},"kaas":{"$ref":"#/components/schemas/KaasInfo"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","region_id","kaas","created_at"]},"KaasInfo":{"type":"object","nullable":true,"properties":{"version":{"type":"string","example":"v1.30"},"status":{"type":"string","enum":["PROVISIONING","RUNNING","RECONCILING","DELETING","ERROR","DEGRADED"],"example":"RUNNING"},"status_message":{"type":"string","nullable":true,"example":null}},"required":["version","status","status_message"],"description":"Present if cluster is KaaS-managed"},"TemplateList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Template"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Template":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"PostgreSQL HA"},"workspace_id":{"type":"string","example":"j572abc123def456"},"registry_proxy_mode":{"type":"string","nullable":true,"enum":["auto","always","never",null],"example":"auto"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","registry_proxy_mode","created_at"]},"TemplateDetail":{"allOf":[{"$ref":"#/components/schemas/Template"},{"type":"object","properties":{"helm_sources":{"type":"array","items":{"$ref":"#/components/schemas/HelmSource"}}},"required":["helm_sources"]}]},"HelmSource":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"chart":{"$ref":"#/components/schemas/HelmSourceChart"},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["id","chart"]},"HelmSourceChart":{"type":"object","properties":{"repo_url":{"type":"string","example":"https://charts.bitnami.com/bitnami"},"chart":{"type":"string","example":"postgresql"},"target_revision":{"type":"string","example":"15.5.0"},"path":{"type":"string","example":"charts/my-chart"}},"required":["repo_url","target_revision"]},"ProductList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Product":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"PostgreSQL Managed"},"workspace_id":{"type":"string","example":"j572abc123def456"},"template_id":{"type":"string","example":"j572abc123def456"},"settings":{"type":"object","nullable":true,"properties":{"custom_image":{"type":"string"},"custom_description":{"type":"string"},"show_sources":{"type":"boolean"}}},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","template_id","settings","created_at"]},"InstallList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Install"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Install":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","nullable":true,"example":"my-postgres"},"workspace_id":{"type":"string","example":"j572abc123def456"},"product_id":{"type":"string","nullable":true,"description":"Set for product-based installs"},"template_id":{"type":"string","nullable":true,"description":"Template driving this install"},"cluster_id":{"type":"string","example":"j572abc123def456"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","workspace_id","product_id","template_id","cluster_id","created_at"]},"Pod":{"type":"object","properties":{"name":{"type":"string","example":"postgres-0"},"containers":{"type":"array","items":{"type":"string"},"example":["postgresql"]}},"required":["name","containers"]},"RegionList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Region"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"Region":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"us-east-1"},"icon":{"type":"string","nullable":true,"description":"Icon URL"},"workspace_id":{"type":"string","example":"j572abc123def456"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"}},"required":["id","name","icon","workspace_id","created_at"]},"RegistryCredentialList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RegistryCredential"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]},"RegistryCredential":{"type":"object","properties":{"id":{"type":"string","example":"j572abc123def456"},"name":{"type":"string","example":"GitHub Container Registry"},"registry_url":{"type":"string","example":"ghcr.io"},"type":{"type":"string","enum":["basic","token","oauth"],"description":"Auth type"},"is_active":{"type":"boolean","description":"Whether the credential is active"},"created_at":{"type":"number","description":"Unix timestamp (seconds)"},"last_used_at":{"type":"number","nullable":true,"description":"Unix timestamp (seconds)"}},"required":["id","name","registry_url","type","is_active","created_at","last_used_at"]}},"parameters":{}},"paths":{"/v1/user/tokens":{"get":{"tags":["Auth"],"summary":"List personal access tokens","description":"Returns metadata for all tokens. Full token values are never shown after creation.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of tokens","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiTokenList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Auth"],"summary":"Create a personal access token","description":"Creates a new PAT. The full token is returned in the response and never shown again.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the token","example":"My CLI token"},"expires_at":{"type":"integer","minimum":0,"exclusiveMinimum":true,"description":"Unix timestamp (seconds) when the token expires. Omit for no expiry.","example":1742169600}},"required":["name"]}}}},"responses":{"201":{"description":"Token created. The `token` field is shown only once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedToken"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/user/tokens/{id}":{"delete":{"tags":["Auth"],"summary":"Revoke a personal access token","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Token ID"},"required":true,"description":"Token ID","name":"id","in":"path"}],"responses":{"204":{"description":"Token revoked"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces":{"get":{"tags":["Workspaces"],"summary":"List workspaces","description":"Lists all workspaces the authenticated user belongs to. No X-Workspace-Id needed.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of workspaces","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{id}":{"get":{"tags":["Workspaces"],"summary":"Get workspace details","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Workspace ID"},"required":true,"description":"Workspace ID","name":"id","in":"path"}],"responses":{"200":{"description":"Workspace details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/clusters":{"get":{"tags":["Clusters"],"summary":"List clusters in workspace","description":"Lists all clusters in the workspace specified by the X-Workspace-Id header.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of clusters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClusterList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/clusters/{id}":{"get":{"tags":["Clusters"],"summary":"Get cluster details","description":"Returns detailed information about a cluster, including KaaS status if applicable.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"responses":{"200":{"description":"Cluster details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cluster"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Clusters"],"summary":"Update cluster","description":"Update cluster name or region. The region must be in the same workspace.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"staging"},"region_id":{"type":"string","description":"Region ID"}}}}}},"responses":{"200":{"description":"Updated cluster","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cluster"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Clusters"],"summary":"Delete cluster","description":"Deletes a cluster and its configuration. KaaS-managed clusters are deprovisioned. Fails if the cluster has active installations.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"responses":{"204":{"description":"Cluster deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cluster has active installations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/clusters/{id}/kubeconfig":{"get":{"tags":["Clusters"],"summary":"Get cluster kubeconfig","description":"Returns the admin kubeconfig for a KaaS-managed cluster. The cluster must be in RUNNING status.\n\nSupports content negotiation via the `Accept` header:\n- `application/json` — returns JSON (default for API clients)\n- `application/yaml` — returns YAML (default for kubectl/CLI)","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Cluster ID"},"required":true,"description":"Cluster ID","name":"id","in":"path"}],"responses":{"200":{"description":"Admin kubeconfig","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"nullable":true},"description":"Kubeconfig as JSON"}},"application/yaml":{"schema":{"type":"string"}}}},"400":{"description":"Cluster is not in RUNNING status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the cluster workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cluster not found or not KaaS-managed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates":{"get":{"tags":["Templates"],"summary":"List templates in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Templates"],"summary":"Create template","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"PostgreSQL HA"},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","properties":{"artifact_hub_helm_package":{"type":"object","additionalProperties":{"nullable":true}},"image":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048},"tag":{"type":"string","minLength":1,"maxLength":200},"github":{"type":"object","properties":{"workflow_run_id":{"type":"string","minLength":1,"maxLength":100},"repository":{"type":"object","properties":{"id":{"type":"number"},"html_url":{"type":"string","minLength":1,"maxLength":2048},"name":{"type":"string","minLength":1,"maxLength":100},"full_name":{"type":"string","minLength":1,"maxLength":200},"owner":{"type":"object","properties":{"login":{"type":"string","minLength":1,"maxLength":100},"id":{"type":"number"},"type":{"type":"string","minLength":1,"maxLength":50},"avatar_url":{"type":"string","minLength":1,"maxLength":2048},"html_url":{"type":"string","minLength":1,"maxLength":2048}},"required":["login","id","type","avatar_url","html_url"]}},"required":["id","html_url","name","full_name","owner"]}}}},"required":["url","tag"]},"auto_deploy":{"type":"boolean"}}}},"required":["chart"]},"minItems":1},"registry_proxy_mode":{"type":"string","enum":["auto","always","never"]}},"required":["name","sources"]}}}},"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"type":"object","properties":{"template_id":{"type":"string"}},"required":["template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates/{id}":{"get":{"tags":["Templates"],"summary":"Get template details","description":"Returns template with its helm sources.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Template ID"},"required":true,"description":"Template ID","name":"id","in":"path"}],"responses":{"200":{"description":"Template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDetail"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Templates"],"summary":"Update template","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Template ID"},"required":true,"description":"Template ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","properties":{"artifact_hub_helm_package":{"type":"object","additionalProperties":{"nullable":true}},"image":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048},"tag":{"type":"string","minLength":1,"maxLength":200},"github":{"type":"object","properties":{"workflow_run_id":{"type":"string","minLength":1,"maxLength":100},"repository":{"type":"object","properties":{"id":{"type":"number"},"html_url":{"type":"string","minLength":1,"maxLength":2048},"name":{"type":"string","minLength":1,"maxLength":100},"full_name":{"type":"string","minLength":1,"maxLength":200},"owner":{"type":"object","properties":{"login":{"type":"string","minLength":1,"maxLength":100},"id":{"type":"number"},"type":{"type":"string","minLength":1,"maxLength":50},"avatar_url":{"type":"string","minLength":1,"maxLength":2048},"html_url":{"type":"string","minLength":1,"maxLength":2048}},"required":["login","id","type","avatar_url","html_url"]}},"required":["id","html_url","name","full_name","owner"]}}}},"required":["url","tag"]},"auto_deploy":{"type":"boolean"}}}},"required":["chart"]},"minItems":1},"registry_proxy_mode":{"type":"string","enum":["auto","always","never"]}}}}}},"responses":{"200":{"description":"Template updated","content":{"application/json":{"schema":{"type":"object","properties":{"template_id":{"type":"string"}},"required":["template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Templates"],"summary":"Delete template","description":"Fails if referenced by products or installs.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Template ID"},"required":true,"description":"Template ID","name":"id","in":"path"}],"responses":{"204":{"description":"Template deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Template is referenced by products or installs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/products":{"get":{"tags":["Products"],"summary":"List products in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Products"],"summary":"Create product","description":"Creates a product with helm sources and cluster associations. Triggers async chart generation.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":32,"example":"PostgreSQL Managed"},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["chart"]},"minItems":1},"cluster_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Cluster IDs to deploy to"},"stripe_price_ids":{"type":"array","items":{"type":"string"}},"settings":{"type":"object","properties":{"custom_image":{"type":"string","minLength":1,"maxLength":2048},"custom_description":{"type":"string","minLength":1,"maxLength":500},"show_sources":{"type":"boolean"}}}},"required":["name","sources","cluster_ids"]}}}},"responses":{"201":{"description":"Product created","content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string"},"template_id":{"type":"string"}},"required":["product_id","template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/products/{id}":{"get":{"tags":["Products"],"summary":"Get product details","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Product ID"},"required":true,"description":"Product ID","name":"id","in":"path"}],"responses":{"200":{"description":"Product details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Products"],"summary":"Update product","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Product ID"},"required":true,"description":"Product ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":32,"example":"PostgreSQL Managed"},"sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["chart"]},"minItems":1},"cluster_ids":{"type":"array","items":{"type":"string"},"minItems":1},"stripe_price_ids":{"type":"array","items":{"type":"string"}},"settings":{"type":"object","properties":{"custom_image":{"type":"string","minLength":1,"maxLength":2048},"custom_description":{"type":"string","minLength":1,"maxLength":500},"show_sources":{"type":"boolean"}}}},"required":["name","sources","cluster_ids"]}}}},"responses":{"200":{"description":"Product updated","content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string"},"template_id":{"type":"string"}},"required":["product_id","template_id"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Products"],"summary":"Delete product","description":"Fails if the product has active installs.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Product ID"},"required":true,"description":"Product ID","name":"id","in":"path"}],"responses":{"204":{"description":"Product deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Product has active installs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs":{"get":{"tags":["Installs"],"summary":"List installs in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of installs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstallList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Installs"],"summary":"Create product install","description":"Deploys a product to a region. Pass an `Idempotency-Key` header to safely retry on timeout.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","example":"deploy-prod-2024-03-15"},"required":false,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","name":"idempotency-key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","minLength":1,"description":"Product ID"},"region_id":{"type":"string","minLength":1,"description":"Region ID"},"overrides":{"type":"array","items":{"type":"object","properties":{"template_helm_source_id":{"type":"string","minLength":1,"description":"Helm source ID to override"},"values":{"type":"object","additionalProperties":{"nullable":true},"description":"Helm values"}},"required":["template_helm_source_id","values"]},"description":"Initial value overrides per helm source"}},"required":["product_id","region_id"]}}}},"responses":{"202":{"description":"Install workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}":{"get":{"tags":["Installs"],"summary":"Get install details","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"responses":{"200":{"description":"Install details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Install"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Installs"],"summary":"Delete install","description":"Triggers async deletion of the install and its resources.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"responses":{"202":{"description":"Deletion started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/pods":{"get":{"tags":["Installs"],"summary":"List pods for install","description":"Returns pods and their containers for the install.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"responses":{"200":{"description":"List of pods","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Pod"}}},"required":["data"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/standalone":{"post":{"tags":["Installs"],"summary":"Create standalone install","description":"Deploys custom helm sources directly to clusters without a product. Pass an `Idempotency-Key` header to safely retry on timeout.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","example":"deploy-prod-2024-03-15"},"required":false,"description":"Unique key to prevent duplicate operations. If the same key is sent again, the existing workflow is returned instead of creating a new one.","name":"idempotency-key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"my-redis"},"cluster_ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Cluster IDs to deploy to"},"helm_sources":{"type":"array","items":{"type":"object","properties":{"chart":{"type":"object","properties":{"repo_url":{"type":"string","minLength":1,"maxLength":2048},"chart":{"type":"string","minLength":1,"maxLength":100},"target_revision":{"type":"string","minLength":1,"maxLength":100},"path":{"type":"string","minLength":1,"maxLength":500}},"required":["repo_url","target_revision"]},"values":{"type":"object","additionalProperties":{"nullable":true}},"metadata":{"type":"object","additionalProperties":{"nullable":true}}},"required":["chart"]},"minItems":1}},"required":["name","cluster_ids","helm_sources"]}}}},"responses":{"202":{"description":"Install workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/values":{"patch":{"tags":["Installs"],"summary":"Update install template values","description":"Updates template helm source values and regenerates the chart. Use this for standalone installs or to change the base values of a product install.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"array","items":{"type":"object","properties":{"template_helm_source_id":{"type":"string","minLength":1,"description":"Helm source ID"},"values":{"type":"object","additionalProperties":{"nullable":true},"description":"Helm values"}},"required":["template_helm_source_id","values"]},"minItems":1}},"required":["updates"]}}}},"responses":{"202":{"description":"Update workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/overrides":{"patch":{"tags":["Installs"],"summary":"Update install value overrides","description":"Applies per-install value overrides on top of the product base values. Does not regenerate the chart.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"array","items":{"type":"object","properties":{"template_helm_source_id":{"type":"string","minLength":1,"description":"Helm source ID"},"values":{"type":"object","additionalProperties":{"nullable":true},"description":"Override values"}},"required":["template_helm_source_id","values"]},"minItems":1}},"required":["updates"]}}}},"responses":{"202":{"description":"Update workflow started"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/installs/{id}/logs":{"get":{"tags":["Installs"],"summary":"Stream install logs","description":"Streams logs from the install pods via Server-Sent Events. Use the `Accept: text/event-stream` header.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Install ID"},"required":true,"description":"Install ID","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":253,"description":"Pod name (all pods if omitted)"},"required":false,"description":"Pod name (all pods if omitted)","name":"pod","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":253,"description":"Container name"},"required":false,"description":"Container name","name":"container","in":"query"},{"schema":{"type":"boolean","nullable":true,"default":true,"description":"Follow log output"},"required":false,"description":"Follow log output","name":"follow","in":"query"},{"schema":{"type":"integer","nullable":true,"description":"Lines to tail"},"required":false,"description":"Lines to tail","name":"tail","in":"query"},{"schema":{"type":"integer","nullable":true,"default":0,"description":"Only return logs newer than this many seconds"},"required":false,"description":"Only return logs newer than this many seconds","name":"since_seconds","in":"query"}],"responses":{"200":{"description":"Log stream (text/event-stream)"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Install not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/regions":{"get":{"tags":["Regions"],"summary":"List regions in workspace","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of regions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Regions"],"summary":"Create region","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"us-east-1"},"icon":{"type":"string","maxLength":256,"format":"uri","description":"Icon URL"}},"required":["name"]}}}},"responses":{"201":{"description":"Region created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Region"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/registry/credentials":{"get":{"tags":["Registry"],"summary":"List registry credentials","description":"Returns credentials for the workspace. Credential secrets are never exposed.","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Pagination cursor from previous response"},"required":false,"description":"Pagination cursor from previous response","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Items per page (1-100)","example":50},"required":false,"description":"Items per page (1-100)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistryCredentialList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Registry"],"summary":"Create registry credential","description":"Adds a registry credential to the workspace for OCI proxy authentication.","security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"GitHub Container Registry"},"registry_url":{"type":"string","minLength":1,"maxLength":2048,"example":"ghcr.io"},"type":{"type":"string","enum":["basic","token","oauth"],"description":"Auth type"},"credentials":{"type":"object","properties":{"username":{"type":"string","minLength":1,"maxLength":200},"password":{"type":"string","minLength":1,"maxLength":500},"token":{"type":"string","minLength":1,"maxLength":2000},"client_id":{"type":"string","minLength":1,"maxLength":200},"client_secret":{"type":"string","minLength":1,"maxLength":500},"token_url":{"type":"string","minLength":1,"maxLength":2048}},"description":"Auth credentials (type-dependent)"}},"required":["name","registry_url","type","credentials"]}}}},"responses":{"201":{"description":"Credential created"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing or invalid workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/registry/credentials/{id}":{"delete":{"tags":["Registry"],"summary":"Delete registry credential","security":[{"BearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Credential ID"},"required":true,"description":"Credential ID","name":"id","in":"path"}],"responses":{"204":{"description":"Credential deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Credential not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}} \ No newline at end of file diff --git a/internal/cmd/auth/auth.go b/internal/cmd/auth/auth.go index d6280b7..cedace4 100644 --- a/internal/cmd/auth/auth.go +++ b/internal/cmd/auth/auth.go @@ -39,7 +39,7 @@ and stores a session token. Sessions are long-lived and auto-refresh on use. With --token, stores the given token directly (PAT or session token). -Create PATs at https://dash.cnap.tech/settings/tokens`, +Create PATs at https://cnap.tech/settings/tokens`, RunE: func(cmd *cobra.Command, args []string) error { cfg, err := config.Load() if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index 5305bcc..973a520 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,7 @@ import ( const ( DefaultAPIURL = "https://api.cnap.tech" - DefaultAuthURL = "https://dash.cnap.tech" + DefaultAuthURL = "https://cnap.tech" configDir = ".cnap" configFile = "config.yaml" )