{"openapi":"3.0.3","info":{"title":"Khotan API","version":"1.0.0"},"servers":[{"url":"https://app.khotan.com"}],"paths":{"/api/v1/integrations":{"get":{"responses":{"200":{"description":"The organization's integrations.","content":{"application/json":{"schema":{"type":"object","properties":{"integrations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"mailbox":{"type":"string"},"name":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["active","revoked","error"]},"scopes":{"type":"array","items":{"type":"string"}},"hasRefreshToken":{"type":"boolean"},"hasAccessToken":{"type":"boolean"},"accessTokenExpiresAt":{"nullable":true,"type":"string"},"lastSyncedAt":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","provider","mailbox","name","status","scopes","hasRefreshToken","hasAccessToken","accessTokenExpiresAt","lastSyncedAt","createdAt","updatedAt"],"additionalProperties":false}}},"required":["integrations"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List integrations","description":"Returns the organization's connected mailbox integrations (first-party, org-owned). Responses never include token material; use the token endpoint to mint an access token.","tags":["Integrations"],"operationId":"listIntegrations","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"200":{"description":"A connect URL for the requested provider.","content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string","enum":["google","microsoft"]},"connectUrl":{"type":"string"}},"required":["provider","connectUrl"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"503":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Start connecting an integration","description":"Begins a Gmail (`google`) or Outlook (`microsoft`) mailbox connection and returns a connect URL. The provider defaults to `google`. OAuth consent happens in a browser, so a human must open the returned URL to authorize; the connection appears in the list once approved. Returns 503 provider_not_configured when the requested provider is not configured in this environment.","tags":["Integrations"],"operationId":"connectIntegration","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"default":"google","type":"string","enum":["google","microsoft"]}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files/uploads":{"post":{"responses":{"200":{"description":"Per-file presigned upload instructions.","content":{"application/json":{"schema":{"type":"object","properties":{"uploads":{"type":"array","items":{"type":"object","properties":{"fileId":{"type":"string"},"name":{"type":"string"},"contentType":{"type":"string"},"size":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"uploadUrl":{"type":"string","description":"Presigned PUT URL; send the file bytes directly here."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Headers that must be sent with the PUT request."},"expiresAt":{"type":"string"}},"required":["fileId","name","contentType","size","uploadUrl","headers","expiresAt"],"additionalProperties":false}}},"required":["uploads"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Prepare file uploads","description":"Validates up to 10 files (1GB each), persists pending records, and returns a presigned PUT URL, required headers, and an expiry per file. PUT each file's bytes to its uploadUrl with the returned headers, then call the complete endpoint with the file ids. Target a folder with folderId or folderPath (mutually exclusive); folderPath creates missing folders along the way. Pending uploads that are never completed expire and are cleaned up automatically.","tags":["Files"],"operationId":"prepareFileUploads","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"files":{"minItems":1,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1},"size":{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991},"type":{"description":"MIME content type, if known.","nullable":true,"type":"string"}},"required":["name","size"]},"description":"Up to 10 files, 1GB each, per request."},"folderId":{"nullable":true,"type":"string","minLength":1},"folderPath":{"description":"Target folder path; missing folders are created (\"mkdir -p\" semantics).","nullable":true,"type":"string","maxLength":4096}},"required":["files"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files/uploads/complete":{"post":{"responses":{"200":{"description":"Per-file completion results.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"fileId":{"type":"string"},"status":{"type":"string","enum":["ready"]},"file":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"contentUrl":{"type":"string","description":"Stable authenticated API URL for opening the file content. It redirects to a short-lived inline storage URL when the file is ready."},"folderId":{"nullable":true,"description":"The containing folder's id, or null for the root.","type":"string"},"path":{"type":"string","description":"The file's full display path, including the file name. Paths change on rename/move; store ids."},"kind":{"type":"string","enum":["archive","audio","document","image","other","pdf","spreadsheet","video"]},"extension":{"nullable":true,"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["pending","ready"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","contentUrl","folderId","path","kind","extension","contentType","sizeBytes","status","createdAt","updatedAt"],"additionalProperties":false}},"required":["fileId","status","file"],"additionalProperties":false},{"type":"object","properties":{"fileId":{"type":"string"},"status":{"type":"string","enum":["failed"]},"error":{"type":"string"}},"required":["fileId","status","error"],"additionalProperties":false}]}}},"required":["results"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Complete file uploads","description":"Verifies each prepared upload (object presence and size) and promotes verified files to ready. Returns a per-file ready or failed result instead of failing the whole batch; failed entries carry an error message (e.g. an expired upload window). Completing an already-completed batch is idempotent.","tags":["Files"],"operationId":"completeFileUploads","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"files":{"minItems":1,"type":"array","items":{"type":"object","properties":{"fileId":{"type":"string","minLength":1}},"required":["fileId"]}}},"required":["files"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files/{fileId}/download":{"get":{"responses":{"200":{"description":"The presigned download URL and its expiry.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Short-lived presigned GET URL with attachment disposition."},"expiresAt":{"type":"string"}},"required":["url","expiresAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a download URL","description":"Returns a short-lived presigned URL (attachment disposition) for a ready file; fetch the bytes directly from that URL before it expires. The URL grants access to the file contents — treat it as a secret and request a fresh one per download. Responds 409 until the file is ready.","tags":["Files"],"operationId":"getFileDownloadUrl","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/integrations/{integrationId}/token":{"post":{"responses":{"200":{"description":"A freshly-minted access token. Secret-bearing.","content":{"application/json":{"schema":{"type":"object","properties":{"accessToken":{"type":"string"},"tokenType":{"type":"string","enum":["Bearer"]},"expiresAt":{"nullable":true,"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}},"required":["accessToken","tokenType","expiresAt","scopes"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"503":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Mint an access token (secret)","description":"Mints a fresh, short-lived OAuth access token for a Gmail or Outlook integration using its stored refresh token, and returns it with its expiry and scopes. The token grants access to the connected mailbox; treat it as a secret. Returns 409 connection_unusable if the grant was revoked at the provider, and 503 provider_not_configured if the integration's provider is not configured in this environment.","tags":["Integrations"],"operationId":"mintIntegrationToken","parameters":[{"name":"integrationId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/metrics":{"get":{"responses":{"200":{"description":"The project's metrics.","content":{"application/json":{"schema":{"type":"object","properties":{"metrics":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string","description":"The name shown: the curated name, else the reported name, else the key."},"displayName":{"nullable":true,"type":"string"},"reportedName":{"nullable":true,"type":"string"},"shape":{"type":"string","enum":["series","breakdown","scatter"]},"grain":{"nullable":true,"type":"string","enum":["day","hour"]},"unit":{"type":"string"},"goal":{"type":"string","enum":["increase","decrease","neutral"],"description":"Which direction of change is good news."},"aggregate":{"type":"string","enum":["sum","average","latest"],"description":"How values across a range combine into one number."},"description":{"nullable":true,"type":"string"},"axes":{"nullable":true,"type":"object","properties":{"xLabel":{"type":"string"},"xUnit":{"type":"string"},"yLabel":{"type":"string"},"yUnit":{"type":"string"}},"required":["xLabel","xUnit","yLabel","yUnit"],"additionalProperties":false},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hidden":{"type":"boolean"},"headline":{"type":"boolean"},"headlineSlot":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastReportedAt":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["key","name","displayName","reportedName","shape","grain","unit","goal","aggregate","description","axes","position","hidden","headline","headlineSlot","lastReportedAt","createdAt","updatedAt"],"additionalProperties":false}}},"required":["metrics"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List project metrics","description":"Returns the project's metrics with their attributes, curation, and last-reported time, in curated order. Hidden metrics are included only for owner, admin, and developer keys.","tags":["Projects"],"operationId":"listProjectMetrics","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"200":{"description":"The metrics and point counts written.","content":{"application/json":{"schema":{"type":"object","properties":{"metrics":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"created":{"type":"boolean"},"pointsWritten":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["key","created","pointsWritten"],"additionalProperties":false}},"metricsWritten":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pointsWritten":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["metrics","metricsWritten","pointsWritten"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A metric exists with a different shape or grain, or the project's metric limit would be exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The report is invalid. `details.metricKey` and `details.pointIndex` identify the offending metric and point.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Report project metrics","description":"Creates or updates metric definitions and sets their points. Idempotent: series and breakdown points are keyed by bucket (and category), scatter points by id, so a retried report never double-counts. The whole report is validated before anything is written. Curation made in Khotan — name, order, hidden, headline — is never overwritten.","tags":["Projects"],"operationId":"reportProjectMetrics","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"metrics":{"minItems":1,"maxItems":50,"type":"array","items":{"oneOf":[{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"name":{"description":"Default display name. Ignored for display once an author renames the metric in Khotan.","nullable":true,"type":"string","minLength":1,"maxLength":200},"description":{"nullable":true,"type":"string","maxLength":280},"unit":{"type":"string","minLength":1,"description":"`count`, `percent` (percentage points, so 45.2 means 45.2%), `duration` (seconds), `currency:<ISO 4217 code>`, or a custom label of at most 16 characters."},"goal":{"type":"string","enum":["increase","decrease","neutral"],"description":"Which direction of change is good news."},"aggregate":{"type":"string","enum":["sum","average","latest"],"description":"How values across a range combine into one number."},"shape":{"type":"string","enum":["series"]},"grain":{"type":"string","enum":["day","hour"]},"points":{"default":[],"type":"array","items":{"type":"object","properties":{"bucket":{"type":"string","description":"`YYYY-MM-DD` (the reporting app's own business date) for day grain; an ISO 8601 timestamp with an offset for hour grain, truncated to the UTC hour."},"value":{"type":"number","description":"A finite number."}},"required":["bucket","value"]}}},"required":["key","unit","goal","aggregate","shape","grain"]},{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"name":{"description":"Default display name. Ignored for display once an author renames the metric in Khotan.","nullable":true,"type":"string","minLength":1,"maxLength":200},"description":{"nullable":true,"type":"string","maxLength":280},"unit":{"type":"string","minLength":1,"description":"`count`, `percent` (percentage points, so 45.2 means 45.2%), `duration` (seconds), `currency:<ISO 4217 code>`, or a custom label of at most 16 characters."},"goal":{"type":"string","enum":["increase","decrease","neutral"],"description":"Which direction of change is good news."},"aggregate":{"type":"string","enum":["sum","average","latest"],"description":"How values across a range combine into one number."},"shape":{"type":"string","enum":["breakdown"]},"grain":{"type":"string","enum":["day","hour"]},"points":{"default":[],"type":"array","items":{"type":"object","properties":{"bucket":{"type":"string","description":"`YYYY-MM-DD` (the reporting app's own business date) for day grain; an ISO 8601 timestamp with an offset for hour grain, truncated to the UTC hour."},"category":{"type":"string","minLength":1,"maxLength":100},"value":{"type":"number","description":"A finite number."}},"required":["bucket","category","value"]}}},"required":["key","unit","goal","aggregate","shape","grain"]},{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"name":{"description":"Default display name. Ignored for display once an author renames the metric in Khotan.","nullable":true,"type":"string","minLength":1,"maxLength":200},"description":{"nullable":true,"type":"string","maxLength":280},"unit":{"type":"string","minLength":1,"description":"`count`, `percent` (percentage points, so 45.2 means 45.2%), `duration` (seconds), `currency:<ISO 4217 code>`, or a custom label of at most 16 characters."},"goal":{"type":"string","enum":["increase","decrease","neutral"],"description":"Which direction of change is good news."},"aggregate":{"type":"string","enum":["sum","average","latest"],"description":"How values across a range combine into one number."},"shape":{"type":"string","enum":["scatter"]},"grain":{"type":"string","nullable":true,"enum":[null]},"xLabel":{"type":"string","minLength":1,"maxLength":64},"xUnit":{"type":"string","minLength":1,"description":"`count`, `percent` (percentage points, so 45.2 means 45.2%), `duration` (seconds), `currency:<ISO 4217 code>`, or a custom label of at most 16 characters."},"yLabel":{"type":"string","minLength":1,"maxLength":64},"yUnit":{"type":"string","minLength":1,"description":"`count`, `percent` (percentage points, so 45.2 means 45.2%), `duration` (seconds), `currency:<ISO 4217 code>`, or a custom label of at most 16 characters."},"points":{"default":[],"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":128,"description":"Client-supplied identity. Reporting an existing id replaces that point."},"x":{"type":"number","description":"A finite number."},"y":{"type":"number","description":"A finite number."},"label":{"nullable":true,"type":"string","maxLength":200},"observedAt":{"nullable":true,"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["id","x","y"]}}},"required":["key","unit","goal","aggregate","shape","xLabel","xUnit","yLabel","yUnit"]}]},"description":"At most 50 metrics and 10000 points in total per report."}},"required":["metrics"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/health":{"get":{"responses":{"200":{"description":"The service is healthy.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]}},"required":["status"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"503":{"description":"The service cannot reach its database.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]}},"required":["status"],"additionalProperties":false}}}}},"summary":"Service readiness probe","description":"Confirms the service is up and can reach its database.","tags":["System"],"operationId":"getHealth"}},"/api/v1/me":{"get":{"responses":{"200":{"description":"The authenticated principal.","content":{"application/json":{"schema":{"type":"object","properties":{"organizationId":{"type":"string"},"role":{"type":"string","enum":["owner","admin","developer","member"]},"keyId":{"type":"string"}},"required":["organizationId","role","keyId"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Identify the caller","description":"Returns the organization, role, and key id resolved from the API key.","tags":["Identity"],"operationId":"getCurrentApiPrincipal","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/api-keys":{"post":{"responses":{"201":{"description":"The minted API key (the secret is returned only once).","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"id":{"type":"string"},"name":{"nullable":true,"type":"string"},"prefix":{"nullable":true,"type":"string"},"organizationId":{"type":"string"},"role":{"type":"string","enum":["owner","admin","developer","member"]},"expiresAt":{"nullable":true,"type":"string"}},"required":["key","id","name","prefix","organizationId","role","expiresAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Exchange credentials for an API key","description":"Authenticates with email + password and mints an organization-scoped API key. Strictly rate limited.","tags":["API keys"],"operationId":"createApiKeyFromCredentials","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":1},"organizationId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":64},"role":{"type":"string","enum":["owner","admin","developer","member"]},"expiresInDays":{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":365}},"required":["email","password","organizationId"]}}}}}},"/api/v1/databases":{"get":{"responses":{"200":{"description":"The organization's databases.","content":{"application/json":{"schema":{"type":"object","properties":{"databases":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"regionId":{"type":"string"},"environment":{"type":"string","enum":["production"]},"postgresVersion":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"providerDatabaseName":{"nullable":true,"type":"string"},"providerRoleName":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","regionId","environment","postgresVersion","providerDatabaseName","providerRoleName","endpointHost","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["databases"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List databases","description":"Returns the organization's non-deleted production databases. Responses never include usage data or connection credentials.","tags":["Databases"],"operationId":"listDatabases","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted database record, in ready or failed status.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"regionId":{"type":"string"},"environment":{"type":"string","enum":["production"]},"postgresVersion":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"providerDatabaseName":{"nullable":true,"type":"string"},"providerRoleName":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","regionId","environment","postgresVersion","providerDatabaseName","providerRoleName","endpointHost","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a database","description":"Provisions a production Postgres database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. This operation is not idempotent: retrying a request that already succeeded provisions a second database. Omit regionId to use the default region.","tags":["Databases"],"operationId":"createDatabase","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"regionId":{"nullable":true,"type":"string","minLength":1},"idempotencyKey":{"type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}":{"get":{"responses":{"200":{"description":"The database record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"regionId":{"type":"string"},"environment":{"type":"string","enum":["production"]},"postgresVersion":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"providerDatabaseName":{"nullable":true,"type":"string"},"providerRoleName":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","regionId","environment","postgresVersion","providerDatabaseName","providerRoleName","endpointHost","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a database","description":"Returns one database's record, including status and failure state when present. Responses never include usage data or connection credentials.","tags":["Databases"],"operationId":"getDatabase","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The database was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The database is protected from deletion (code resource_protected). Remove protection from the dashboard before deleting via the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Delete a database","description":"Tears down the provider project, then soft-deletes the record — the same semantics as deleting from the dashboard. Deletion is permanent. Returns 403 resource_protected when the database has deletion protection enabled; protection can only be removed from the dashboard.","tags":["Databases"],"operationId":"deleteDatabase","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/tags":{"put":{"responses":{"200":{"description":"The stored tag list, after normalization.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set a database's tags","description":"Replaces a database's tag list with the submitted list and returns the stored list. Tags are normalized to lowercase slugs, so `Live` is stored as `live`. Send an empty list to remove every tag. Requires an API key whose role is owner or developer.","tags":["Databases"],"operationId":"setDatabaseTags","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}},"required":["tags"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/connection":{"get":{"responses":{"200":{"description":"The connection URI for the ready database.","content":{"application/json":{"schema":{"type":"object","properties":{"connectionUri":{"type":"string"}},"required":["connectionUri"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get the connection URI","description":"Returns the full Postgres connection URI for a ready database. The URI grants complete read/write access to the database; treat it as a secret. Responds 404 until the database is ready. All schema and row access happens directly over Postgres using this URI — the API never proxies SQL.","tags":["Databases"],"operationId":"getDatabaseConnection","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/rotate-credentials":{"post":{"responses":{"200":{"description":"The new connection URI after rotation.","content":{"application/json":{"schema":{"type":"object","properties":{"connectionUri":{"type":"string"}},"required":["connectionUri"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Another rotation is already in progress for this database; retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rotate database credentials","description":"Resets the database role password through the provider and returns the new connection URI; the previous URI stops authenticating immediately. Rotation drops any live connections or pools still using the old credentials — plan to reconnect. Rotation is idempotent: if a rotation reports a persistence failure, rotating again issues a fresh working connection string.","tags":["Databases"],"operationId":"rotateDatabaseCredentials","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/branches":{"get":{"responses":{"200":{"description":"The database's branches.","content":{"application/json":{"schema":{"type":"object","properties":{"branches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"databaseId":{"type":"string"},"parentBranchId":{"nullable":true,"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"initSource":{"type":"string","enum":["parent-data","schema-only"]},"parentTimestamp":{"nullable":true,"type":"string"},"expiresAt":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","databaseId","parentBranchId","name","status","initSource","parentTimestamp","expiresAt","endpointHost","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["branches"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List database branches","description":"Returns the database's non-deleted branches. Responses never include connection credentials.","tags":["Databases"],"operationId":"listDatabaseBranches","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted branch record, in ready or failed status.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"databaseId":{"type":"string"},"parentBranchId":{"nullable":true,"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"initSource":{"type":"string","enum":["parent-data","schema-only"]},"parentTimestamp":{"nullable":true,"type":"string"},"expiresAt":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","databaseId","parentBranchId","name","status","initSource","parentTimestamp","expiresAt","endpointHost","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a database branch","description":"Branches a ready database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. Omit parentBranchId to branch from the database root. Expiration: omit expiresAt and noExpiry for the ephemeral default, pass expiresAt (ISO 8601) for an explicit expiry, or noExpiry true for a non-expiring branch. This operation is not idempotent.","tags":["Databases"],"operationId":"createDatabaseBranch","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":256},"parentBranchId":{"nullable":true,"type":"string","minLength":1},"initSource":{"type":"string","enum":["parent-data","schema-only"]},"parentTimestamp":{"nullable":true,"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"expiresAt":{"nullable":true,"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"noExpiry":{"nullable":true,"type":"boolean"}},"required":["name","initSource"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/branches/{branchId}":{"get":{"responses":{"200":{"description":"The branch record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"databaseId":{"type":"string"},"parentBranchId":{"nullable":true,"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"initSource":{"type":"string","enum":["parent-data","schema-only"]},"parentTimestamp":{"nullable":true,"type":"string"},"expiresAt":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","databaseId","parentBranchId","name","status","initSource","parentTimestamp","expiresAt","endpointHost","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a database branch","description":"Returns one branch's record, including status and failure state when present. Responses never include connection credentials.","tags":["Databases"],"operationId":"getDatabaseBranch","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"branchId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The branch was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The branch still has child branches (code branch_has_children). Delete the descendants first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Delete a database branch","description":"Tears down the provider branch and its compute, then soft-deletes the record. Deletion is permanent. Returns 409 branch_has_children when the branch still has child branches; delete the descendants first.","tags":["Databases"],"operationId":"deleteDatabaseBranch","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"branchId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/branches/{branchId}/connection":{"get":{"responses":{"200":{"description":"The connection URI for the ready branch.","content":{"application/json":{"schema":{"type":"object","properties":{"connectionUri":{"type":"string"}},"required":["connectionUri"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a branch connection URI","description":"Returns the full Postgres connection URI for a ready branch. The URI grants complete read/write access to the branch; treat it as a secret. Responds 404 until the branch is ready. All schema and row access happens directly over Postgres using this URI — the API never proxies SQL.","tags":["Databases"],"operationId":"getDatabaseBranchConnection","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"branchId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/branches/{branchId}/rotate-credentials":{"post":{"responses":{"200":{"description":"The new connection URI after rotation.","content":{"application/json":{"schema":{"type":"object","properties":{"connectionUri":{"type":"string"}},"required":["connectionUri"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Another rotation is already in progress for this branch; retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rotate branch credentials","description":"Resets the branch role password through the provider and returns the new connection URI; the previous URI stops authenticating immediately. Rotation drops any live connections or pools still using the old credentials — plan to reconnect. Rotation is idempotent: if a rotation reports a persistence failure, rotating again issues a fresh working connection string.","tags":["Databases"],"operationId":"rotateDatabaseBranchCredentials","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"branchId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/redis-databases":{"get":{"responses":{"200":{"description":"The organization's Redis databases.","content":{"application/json":{"schema":{"type":"object","properties":{"databases":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"primaryRegion":{"type":"string"},"readRegions":{"nullable":true,"type":"array","items":{"type":"string"}},"endpointHost":{"nullable":true,"type":"string"},"port":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tls":{"type":"boolean"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","primaryRegion","readRegions","endpointHost","port","tls","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["databases"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List Redis databases","description":"Returns the organization's non-deleted Redis databases. Responses never include connection credentials.","tags":["Redis Databases"],"operationId":"listRedisDatabases","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted Redis database record, ready or failed.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"primaryRegion":{"type":"string"},"readRegions":{"nullable":true,"type":"array","items":{"type":"string"}},"endpointHost":{"nullable":true,"type":"string"},"port":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tls":{"type":"boolean"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","primaryRegion","readRegions","endpointHost","port","tls","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a Redis database","description":"Provisions a globally replicated Redis database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. This operation is not idempotent: retrying a request that already succeeded provisions a second database. Omit primaryRegion to use the default region.","tags":["Redis Databases"],"operationId":"createRedisDatabase","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"primaryRegion":{"nullable":true,"type":"string","minLength":1},"idempotencyKey":{"type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/redis-databases/{databaseId}":{"get":{"responses":{"200":{"description":"The Redis database record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"primaryRegion":{"type":"string"},"readRegions":{"nullable":true,"type":"array","items":{"type":"string"}},"endpointHost":{"nullable":true,"type":"string"},"port":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tls":{"type":"boolean"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","primaryRegion","readRegions","endpointHost","port","tls","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a Redis database","description":"Returns one Redis database's record, including status and failure state when present. Responses never include connection credentials.","tags":["Redis Databases"],"operationId":"getRedisDatabase","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The Redis database was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The database is protected from deletion (code resource_protected). Remove protection from the dashboard before deleting via the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Delete a Redis database","description":"Tears down the database at the provider, then soft-deletes the record — the same semantics as deleting from the dashboard. Deletion is permanent. Returns 403 resource_protected when the database has deletion protection enabled; protection can only be removed from the dashboard.","tags":["Redis Databases"],"operationId":"deleteRedisDatabase","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/redis-databases/{databaseId}/tags":{"put":{"responses":{"200":{"description":"The stored tag list, after normalization.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set a Redis database's tags","description":"Replaces a Redis database's tag list with the submitted list and returns the stored list. Tags are normalized to lowercase slugs, so `Live` is stored as `live`. Send an empty list to remove every tag. Requires an API key whose role is owner or developer.","tags":["Redis Databases"],"operationId":"setRedisDatabaseTags","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}},"required":["tags"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/redis-databases/{databaseId}/connection":{"get":{"responses":{"200":{"description":"The Redis connection credentials for the ready database.","content":{"application/json":{"schema":{"type":"object","properties":{"restUrl":{"type":"string","format":"uri"},"restToken":{"type":"string","minLength":1},"readOnlyRestToken":{"type":"string","minLength":1},"redisUrl":{"type":"string","format":"uri"}},"required":["restUrl","restToken","readOnlyRestToken","redisUrl"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get Redis connection credentials","description":"Returns the rediss:// URI plus the REST URL and read/write and read-only REST tokens for a ready Redis database. These credentials grant access to the database; treat them as secrets. Responds 404 until the database is ready.","tags":["Redis Databases"],"operationId":"getRedisDatabaseConnection","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/buckets":{"get":{"responses":{"200":{"description":"The organization's storage buckets.","content":{"application/json":{"schema":{"type":"object","properties":{"buckets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"accessMode":{"type":"string","enum":["private","public"]},"rootFolderId":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","accessMode","rootFolderId","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["buckets"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List buckets","description":"Returns the organization's non-deleted storage buckets. Responses never include a credential; issue one with the credentials operation.","tags":["Buckets"],"operationId":"listBuckets","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted bucket record, ready or failed.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"accessMode":{"type":"string","enum":["private","public"]},"rootFolderId":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","accessMode","rootFolderId","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a bucket","description":"Provisions a Khotan-managed storage bucket synchronously and responds 201 with the persisted record whether setup ends ready or failed; branch on the returned status. A bucket is a private folder subtree in your organization's file storage, reached through the Files API with a scoped key. Supply an idempotencyKey to make retries safe — a retry carrying the same key returns the original bucket instead of creating a second one; without a key, every call creates a new bucket.","tags":["Buckets"],"operationId":"createBucket","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"idempotencyKey":{"type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/buckets/{bucketId}":{"get":{"responses":{"200":{"description":"The bucket record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"accessMode":{"type":"string","enum":["private","public"]},"rootFolderId":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","accessMode","rootFolderId","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a bucket","description":"Returns one storage bucket's record, including status and failure state when present. Responses never include a credential; issue one with the credentials operation.","tags":["Buckets"],"operationId":"getBucket","parameters":[{"name":"bucketId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The storage was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The storage is protected from deletion (code resource_protected). Remove protection from the dashboard before deleting via the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a bucket","description":"Empties the bucket's folder subtree (deleting its objects), revokes its scoped credentials, then soft-deletes the record — the same semantics as deleting from the dashboard. Deletion is permanent. Returns 403 resource_protected when the bucket has deletion protection enabled; protection can only be removed from the dashboard.","tags":["Buckets"],"operationId":"deleteBucket","parameters":[{"name":"bucketId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/buckets/{bucketId}/tags":{"put":{"responses":{"200":{"description":"The stored tag list, after normalization.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set a bucket's tags","description":"Replaces a bucket's tag list with the submitted list and returns the stored list. Tags are normalized to lowercase slugs, so `Live` is stored as `live`. Send an empty list to remove every tag. Requires an API key whose role is owner or developer.","tags":["Buckets"],"operationId":"setBucketTags","parameters":[{"name":"bucketId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}},"required":["tags"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/buckets/{bucketId}/credentials":{"post":{"responses":{"200":{"description":"The issued bucket credential (secret shown once).","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"The API key secret. Send it as `Authorization: Bearer <apiKey>` to the Files API. Shown once — store it now; rotate to replace it."},"keyId":{"type":"string"},"prefix":{"nullable":true,"type":"string"},"filesApiBaseUrl":{"type":"string","description":"Base URL for the Files API this key is authorized against. Already includes the `/api/v1` prefix — append resource paths such as `/files/...` directly. Do not append another `/api/v1`."}},"required":["apiKey","keyId","prefix","filesApiBaseUrl"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Issue bucket credentials","description":"Issues a folder-scoped API key for the bucket and returns its secret once. The key acts only through the Files API and only within the bucket's folder subtree — send it as Authorization: Bearer against the Files API. Each call rotates the credential: any previously issued key for this bucket is revoked. The secret is shown once and is not retrievable afterward. The response filesApiBaseUrl already includes /api/v1 — append paths such as /files/... directly; do not append another /api/v1.","tags":["Buckets"],"operationId":"issueBucketCredentials","parameters":[{"name":"bucketId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The bucket credential was revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"boolean","enum":[true]}},"required":["revoked"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Revoke bucket credentials","description":"Revokes the bucket's live folder-scoped API key and clears the stored secret without minting a replacement. Subsequent Files API calls with the revoked key fail authentication. Issue a new key with POST /api/v1/buckets/{bucketId}/credentials when one is needed again.","tags":["Buckets"],"operationId":"revokeBucketCredentials","parameters":[{"name":"bucketId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps":{"get":{"responses":{"200":{"description":"The organization's apps.","content":{"application/json":{"schema":{"type":"object","properties":{"apps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"source":{"type":"string","enum":["managed","external"]},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"primaryHostname":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentDeployment":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","building","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"envChangedAt":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","source","status","lifecycle","regionPreference","liveUrl","primaryHostname","repo","currentDeployment","envChangedAt","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["apps"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List apps","description":"Returns the organization's non-deleted apps, including each app's current deployment. Deployment state is reconciled with the provider before reading (staleness-gated), so polling this endpoint observes deployments reaching a terminal status. Responses never include usage data.","tags":["Apps"],"operationId":"listApps","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted app record, in ready or failed status.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"source":{"type":"string","enum":["managed","external"]},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"primaryHostname":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentDeployment":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","building","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"envChangedAt":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","source","status","lifecycle","regionPreference","liveUrl","primaryHostname","repo","currentDeployment","envChangedAt","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create an app","description":"Provisions an app synchronously (template repo plus provider project) and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. Requires an API key whose role includes developer. Pass an idempotencyKey to make retries safe: repeating a create with the same key returns the existing app instead of provisioning a duplicate. Initial environment variables are applied to the provider project before the first deployment. Omit regionPreference to use the default region. Omit domain to put the app's host on the default Khotan-managed namespace; an unavailable domain returns 400 invalid_domain.","tags":["Apps"],"operationId":"createApp","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"regionPreference":{"nullable":true,"type":"string","minLength":1},"domain":{"description":"Khotan-managed domain namespace for the app's host, such as khotan.app or apps.khotan.com. Omit to use the default namespace.","nullable":true,"type":"string","minLength":1,"maxLength":253},"environmentVariables":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"value":{"type":"string","maxLength":65536}},"required":["key","value"]}},"database":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["existing"]},"databaseId":{"type":"string","minLength":1}},"required":["mode","databaseId"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["create"]},"displayName":{"type":"string","minLength":1,"maxLength":63},"regionId":{"type":"string","minLength":1}},"required":["mode","displayName","regionId"],"additionalProperties":false}]},"redis":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["existing"]},"databaseId":{"type":"string","minLength":1}},"required":["mode","databaseId"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["create"]},"displayName":{"type":"string","minLength":1,"maxLength":63},"primaryRegion":{"type":"string","minLength":1}},"required":["mode","displayName","primaryRegion"],"additionalProperties":false}]},"idempotencyKey":{"type":"string","minLength":1,"maxLength":255}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/external":{"post":{"responses":{"200":{"description":"The existing external app matched by its references.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"source":{"type":"string","enum":["managed","external"]},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"primaryHostname":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentDeployment":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","building","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"envChangedAt":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","source","status","lifecycle","regionPreference","liveUrl","primaryHostname","repo","currentDeployment","envChangedAt","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"201":{"description":"The newly linked external app.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"source":{"type":"string","enum":["managed","external"]},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"primaryHostname":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentDeployment":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","building","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"envChangedAt":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","source","status","lifecycle","regionPreference","liveUrl","primaryHostname","repo","currentDeployment","envChangedAt","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Link an external app","description":"Registers an existing hosted project (deployed outside Khotan) as an external app so it can be attached to a project. Provide the app name, hosting project id or slug, production URL, GitHub repository URL, and default branch. Linking the same references again returns the existing record.","tags":["Apps"],"operationId":"linkExternalApp","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"vercelProjectId":{"type":"string","minLength":1,"maxLength":256},"productionUrl":{"type":"string","minLength":1,"maxLength":2048},"githubRepositoryUrl":{"type":"string","minLength":1,"maxLength":2048},"defaultBranch":{"type":"string","minLength":1,"maxLength":255}},"required":["name","vercelProjectId","productionUrl","githubRepositoryUrl","defaultBranch"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}":{"get":{"responses":{"200":{"description":"The app record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"source":{"type":"string","enum":["managed","external"]},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"primaryHostname":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentDeployment":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","building","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"envChangedAt":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","source","status","lifecycle","regionPreference","liveUrl","primaryHostname","repo","currentDeployment","envChangedAt","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get an app","description":"Returns one app's record, including its current deployment and failure state when present. Deployment state is reconciled with the provider before reading (staleness-gated), so poll this endpoint after a create or redeploy to observe the deployment reaching ready or failed. Responses never include usage data.","tags":["Apps"],"operationId":"getApp","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The app was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The app is protected from deletion (code resource_protected). Remove protection from the dashboard before deleting via the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Delete an app","description":"Tears down the provider project and repo, then soft-deletes the record — the same semantics as deleting from the dashboard. Requires an API key whose role includes developer. Deletion is permanent. Returns 403 resource_protected when the app has deletion protection enabled; protection can only be removed from the dashboard.","tags":["Apps"],"operationId":"deleteApp","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/tags":{"put":{"responses":{"200":{"description":"The stored tag list, after normalization.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set an app's tags","description":"Replaces an app's tag list with the submitted list and returns the stored list. Tags are normalized to lowercase slugs, so `Live` is stored as `live`. Send an empty list to remove every tag. Requires an API key whose role is owner, admin, or developer.","tags":["Apps"],"operationId":"setAppTags","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}},"required":["tags"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/redeploy":{"post":{"responses":{"202":{"description":"The new deployment's initial status.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["queued","building","ready","failed","canceled"]}},"required":["status"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Redeploy an app","description":"Triggers a fresh production deployment from the tip of the app's linked branch and responds 202 with the new deployment's initial status (queued or building). Poll the app detail endpoint to observe the deployment reaching ready or failed. The new build picks up the currently-applied environment variables.","tags":["Apps"],"operationId":"redeployApp","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/setup":{"post":{"responses":{"202":{"description":"The new run, in status running.","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["app_setup"]},"resourceKind":{"type":"string","enum":["apps","pipelines"]},"resourceId":{"type":"string"},"status":{"type":"string","enum":["running","ready","needs_person","failed","expired"]},"reason":{"nullable":true,"type":"string"},"pullRequestUrl":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"agentUrl":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"finishedAt":{"nullable":true,"type":"string"}},"required":["id","kind","resourceKind","resourceId","status","reason","pullRequestUrl","liveUrl","agentUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["run"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"503":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Start app setup","description":"Starts a cloud setup run for the app: Khotan launches a cloud agent against the organization's repository with the intake, gives it a short-lived developer key, and merges its setup PR when every guard holds. Responds 202 with the run in status running; poll GET /api/v1/apps/{appId}/setup to follow it. Requires an API key whose role includes developer, created by a member. Only one run may be running per organization. Pass an idempotencyKey to make retries safe.","tags":["Apps"],"operationId":"startAppSetup","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":20,"pattern":"^[A-Za-z0-9][A-Za-z0-9 &.-]*$"},"website":{"type":"string","format":"uri"},"logo":{"nullable":true,"type":"string","format":"uri"},"idempotencyKey":{"description":"Repeat a start with the same key to get the same run back instead of starting a second one.","type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"get":{"responses":{"200":{"description":"The newest run, or null.","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["app_setup"]},"resourceKind":{"type":"string","enum":["apps","pipelines"]},"resourceId":{"type":"string"},"status":{"type":"string","enum":["running","ready","needs_person","failed","expired"]},"reason":{"nullable":true,"type":"string"},"pullRequestUrl":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"agentUrl":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"finishedAt":{"nullable":true,"type":"string"}},"required":["id","kind","resourceKind","resourceId","status","reason","pullRequestUrl","liveUrl","agentUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["run"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get app setup","description":"Returns the app's newest setup run, with its status, the reason for any status other than ready, the setup PR, and the live URL once it is ready. Returns { run: null } when setup never ran through Khotan.","tags":["Apps"],"operationId":"getAppSetup","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/setup/pull-request":{"post":{"responses":{"200":{"description":"The setup PR, and whether it merged.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"number":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"merged":{"type":"boolean"},"mergeCommitSha":{"nullable":true,"type":"string"}},"required":["url","number","merged","mergeCommitSha"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Open or merge the app's setup PR","description":"Opens the setup PR from the app's cursor/setup-<slug> branch into the default branch, or updates the open one, through Khotan's GitHub app. The setup script calls it when gh cannot. With merge: true, Khotan merges with a merge commit only when the caller is the running setup run's own key, the PR changes only the app, its database package, bun.lock, khotan.bindings.json, and skills-lock.json, and every check on the head commit passed. A refused merge returns 409 with the guard's code and leaves the PR open.","tags":["Apps"],"operationId":"upsertAppSetupPullRequest","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":256},"body":{"type":"string","maxLength":65536},"merge":{"description":"Merge the PR when every guard holds. Only the running setup run's own key can merge.","type":"boolean"}},"required":["title","body"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/deployments":{"get":{"responses":{"200":{"description":"The app's recent deployments.","content":{"application/json":{"schema":{"type":"object","properties":{"deployments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"target":{"nullable":true,"type":"string"},"url":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"gitCommitMessage":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"}},"required":["id","status","target","url","gitBranch","gitCommitSha","gitCommitMessage","createdAt","readyAt","failedAt"],"additionalProperties":false}}},"required":["deployments"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List an app's deployments","description":"Returns the app's recent deployments, newest first, read live from the hosting provider: every deployment, including ones started by a git push, with state, branch, commit, and times. Falls back to the deployments Khotan recorded when the provider can't be read.","tags":["Apps"],"operationId":"listAppDeployments","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/source":{"get":{"responses":{"200":{"description":"The app's source files.","content":{"application/json":{"schema":{"type":"object","properties":{"subdirectory":{"nullable":true,"type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"encoding":{"type":"string","enum":["utf-8","base64"]},"content":{"type":"string"}},"required":["path","encoding","content"],"additionalProperties":false}}},"required":["subdirectory","files"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The app exists but its source repository is not ready yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The source is too large to download as a single response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Download an app's source","description":"Returns the app's source files (its subdirectory inside the org mono-root repo) as a JSON listing with paths rooted at the app. Text files are returned as utf-8, binary files as base64. Responds 409 source_not_ready when the app has no repo yet, and 413 source_too_large for sources past the inline download limit.","tags":["Apps"],"operationId":"getAppSource","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/env":{"get":{"responses":{"200":{"description":"Environment variable metadata, never values.","content":{"application/json":{"schema":{"type":"object","properties":{"variables":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"target":{"type":"array","items":{"type":"string","enum":["production","preview","development"]}},"type":{"type":"string"},"updatedAt":{"nullable":true,"type":"string"},"differsAcrossTargets":{"type":"boolean"}},"required":["key","target","type","updatedAt","differsAcrossTargets"],"additionalProperties":false}}},"required":["variables"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"List environment variables","description":"Returns environment variable metadata (key, targets, type, last update) for the app's provider project. Requires an API key whose role includes developer. Values are never included; use the reveal endpoint to fetch one decrypted value.","tags":["Apps"],"operationId":"listAppEnvVars","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"200":{"description":"The partial result: which keys were upserted and which failed.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"}},"required":["upsertedKeys","failedKeys","requiresRedeploy"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Bulk upsert environment variables","description":"Creates or updates many environment variables for the given environments (defaults to all when target is omitted) in one request; other environments keep their value. Requires an API key whose role includes developer. Writes are not transactional: the response reports upsertedKeys and failedKeys, and earlier successful writes remain applied — retry the failed subset. Changes apply on the next deployment; trigger a redeploy to make them live.","tags":["Apps"],"operationId":"bulkUpsertAppEnvVars","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"variables":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"value":{"type":"string","maxLength":65536}},"required":["key","value"]}},"target":{"minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["production","preview","development"]}}},"required":["variables"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/env/{key}":{"get":{"responses":{"200":{"description":"The decrypted value.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}},"required":["key","value"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Reveal an environment variable","description":"Returns the decrypted value of one environment variable for a single environment (defaults to production; pass ?target=preview|development). Treat the value as a secret. Every reveal is audit-logged with the key name and caller; the value itself is never logged.","tags":["Apps"],"operationId":"revealAppEnvVar","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"}},{"name":"target","in":"query","required":false,"schema":{"type":"string","enum":["production","preview","development"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"put":{"responses":{"200":{"description":"The variable was written; a redeploy is required.","content":{"application/json":{"schema":{"type":"object","properties":{"requiresRedeploy":{"type":"boolean","enum":[true]}},"required":["requiresRedeploy"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Upsert an environment variable","description":"Creates or updates one environment variable for the given environments (defaults to all when target is omitted); other environments keep their value. Changes apply on the next deployment; trigger a redeploy to make them live.","tags":["Apps"],"operationId":"upsertAppEnvVar","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","maxLength":65536},"target":{"minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["production","preview","development"]}}},"required":["value"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The variable was removed (requiresRedeploy=true) or did not exist (requiresRedeploy=false).","content":{"application/json":{"schema":{"type":"object","properties":{"requiresRedeploy":{"type":"boolean"}},"required":["requiresRedeploy"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete an environment variable","description":"Removes one environment variable's value from the given environment (pass ?target=production|preview|development; omit to remove from all). Deleting a value that does not exist succeeds with requiresRedeploy=false. When a value is removed, changes apply on the next deployment; trigger a redeploy to make them live.","tags":["Apps"],"operationId":"deleteAppEnvVar","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"}},{"name":"target","in":"query","required":false,"schema":{"type":"string","enum":["production","preview","development"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/email-subscriptions":{"get":{"responses":{"200":{"description":"The deployment's email subscriptions.","content":{"application/json":{"schema":{"type":"object","properties":{"subscriptions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"emailIntegrationId":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"target":{"type":"object","properties":{"type":{"type":"string","enum":["app","pipeline"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"deliveryUrl":{"type":"string"},"status":{"type":"string","enum":["pending","active","failed","disabled"]},"expiresAt":{"nullable":true,"type":"string"},"failure":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","emailIntegrationId","provider","target","deliveryUrl","status","expiresAt","failure","createdAt","updatedAt"],"additionalProperties":false}}},"required":["subscriptions"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List a deployment's email subscriptions","description":"Lists this deployment's mailbox subscriptions. Each delivers new-message notifications from a connected Gmail or Outlook mailbox directly to its delivery URL. Responses never include the Outlook client state.","tags":["Apps"],"operationId":"listAppEmailSubscriptions","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created subscription. For Outlook it includes the clientState secret.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"emailIntegrationId":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"target":{"type":"object","properties":{"type":{"type":"string","enum":["app","pipeline"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"deliveryUrl":{"type":"string"},"status":{"type":"string","enum":["pending","active","failed","disabled"]},"expiresAt":{"nullable":true,"type":"string"},"failure":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"clientState":{"nullable":true,"type":"string"}},"required":["id","emailIntegrationId","provider","target","deliveryUrl","status","expiresAt","failure","createdAt","updatedAt","clientState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Subscribe a deployment to a mailbox","description":"Subscribes this deployment to a connected Gmail or Outlook mailbox so the provider delivers new-message notifications directly to deliveryUrl (https). Gmail: Khotan registers the mailbox watch and a Pub/Sub push subscription whose pushes carry a Google OIDC token. Outlook: Khotan creates a Microsoft Graph subscription — Graph first validates deliveryUrl by POSTing a validationToken query parameter that the receiver must echo as text/plain within 10 seconds, and every later notification carries the clientState returned here. Secret-bearing and never cached; recover a lost clientState from the client-state endpoint.","tags":["Apps"],"operationId":"createAppEmailSubscription","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emailIntegrationId":{"type":"string","minLength":1},"deliveryUrl":{"type":"string","format":"uri"}},"required":["emailIntegrationId","deliveryUrl"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/email-subscriptions/{subscriptionId}":{"get":{"responses":{"200":{"description":"The subscription.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"emailIntegrationId":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"target":{"type":"object","properties":{"type":{"type":"string","enum":["app","pipeline"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"deliveryUrl":{"type":"string"},"status":{"type":"string","enum":["pending","active","failed","disabled"]},"expiresAt":{"nullable":true,"type":"string"},"failure":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","emailIntegrationId","provider","target","deliveryUrl","status","expiresAt","failure","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a deployment's email subscription","tags":["Apps"],"operationId":"getAppEmailSubscription","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The subscription was removed.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Unsubscribe a deployment from a mailbox","description":"Stops delivery to this deployment. Gmail: deletes the Pub/Sub push subscription and, when nothing else subscribes to that mailbox, stops the Gmail watch and removes the topic. Outlook: deletes the Microsoft Graph subscription (already-missing counts as deleted); returns 502 provider_error and keeps the subscription if Graph refuses, so the call can be retried.","tags":["Apps"],"operationId":"deleteAppEmailSubscription","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/email-subscriptions/{subscriptionId}/client-state":{"get":{"responses":{"200":{"description":"The subscription's client state. Secret-bearing.","content":{"application/json":{"schema":{"type":"object","properties":{"subscriptionId":{"type":"string"},"clientState":{"type":"string"}},"required":["subscriptionId","clientState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a deployment's Outlook subscription client state (secret)","description":"Returns the Outlook subscription's clientState — the secret Microsoft Graph sends with every notification to the delivery URL. Compare it in constant time and reject notifications that don't match. Treat it as a secret. Returns 400 client_state_unsupported for Gmail subscriptions, which are verified by Pub/Sub OIDC instead.","tags":["Apps"],"operationId":"getAppEmailSubscriptionClientState","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/oauth":{"get":{"responses":{"200":{"description":"The OAuth client status.","content":{"application/json":{"schema":{"type":"object","properties":{"client":{"nullable":true,"type":"object","properties":{"clientId":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"extraRedirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"callbackPath":{"type":"string"},"createdAt":{"type":"string"}},"required":["clientId","issuer","redirectUris","extraRedirectUris","callbackPath","createdAt"],"additionalProperties":false},"canGenerate":{"type":"boolean"},"unavailableReason":{"nullable":true,"type":"string","enum":["no_provider_project","no_primary_hostname"]}},"required":["client","canGenerate","unavailableReason"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get app OAuth status","description":"Returns non-secret OAuth client status, effective redirect URIs, and setup availability.","tags":["Apps"],"operationId":"getAppOAuthStatus","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The OAuth client was disconnected.","content":{"application/json":{"schema":{"type":"object","properties":{"disconnected":{"type":"boolean"}},"required":["disconnected"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Disconnect app OAuth","description":"Revokes the OAuth client and removes its injected environment variables.","tags":["Apps"],"operationId":"disconnectAppOAuth","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/oauth/setup":{"post":{"responses":{"200":{"description":"The new OAuth credentials.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"},"outcome":{"type":"string","enum":["created","rotated"]},"clientId":{"type":"string"},"clientSecret":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["upsertedKeys","failedKeys","requiresRedeploy","outcome","clientId","clientSecret","issuer","redirectUris"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set up app OAuth","description":"Creates the OAuth client once and returns its plaintext secret. Returns a conflict when credentials already exist.","tags":["Apps"],"operationId":"setupAppOAuth","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/oauth/sync":{"post":{"responses":{"200":{"description":"The synchronized OAuth configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"},"outcome":{"type":"string","enum":["synced"]},"clientId":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["upsertedKeys","failedKeys","requiresRedeploy","outcome","clientId","issuer","redirectUris"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Sync app OAuth","description":"Repairs redirect and environment configuration without rotating the client secret.","tags":["Apps"],"operationId":"syncAppOAuth","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/oauth/rotate":{"post":{"responses":{"200":{"description":"The rotated credentials and redeploy result.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"},"outcome":{"type":"string","enum":["rotated"]},"clientId":{"type":"string"},"clientSecret":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"redeploy":{"type":"object","properties":{"requested":{"type":"boolean"},"started":{"type":"boolean"},"status":{"nullable":true,"type":"string","enum":["queued","building","running","ready","failed","canceled"]},"error":{"nullable":true,"type":"string"}},"required":["requested","started","status","error"],"additionalProperties":false}},"required":["upsertedKeys","failedKeys","requiresRedeploy","outcome","clientId","clientSecret","issuer","redirectUris","redeploy"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rotate app OAuth secret","description":"Invalidates the current secret, injects a new one, and optionally starts a fresh deployment.","tags":["Apps"],"operationId":"rotateAppOAuth","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redeploy":{"default":false,"type":"boolean"}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/oauth/redirect-uris":{"put":{"responses":{"200":{"description":"The effective redirect URI set.","content":{"application/json":{"schema":{"type":"object","properties":{"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["redirectUris"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Replace app OAuth redirect URIs","description":"Replaces operator-managed redirect URIs while preserving the managed production callback.","tags":["Apps"],"operationId":"setAppOAuthRedirectUris","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redirectUris":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","maxLength":2048,"format":"uri"}}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/apps/{appId}/oauth/export":{"post":{"responses":{"200":{"description":"The .env.local content.","content":{"application/json":{"schema":{"type":"string"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Export app OAuth environment","description":"Returns secret-bearing .env.local content for the existing OAuth client.","tags":["Apps"],"operationId":"exportAppOAuth","parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines":{"get":{"responses":{"200":{"description":"The organization's pipelines.","content":{"application/json":{"schema":{"type":"object","properties":{"pipelines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"runtime":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentRun":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","running","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","status","lifecycle","regionPreference","runtime","repo","currentRun","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["pipelines"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List pipelines","description":"Returns the organization's non-deleted pipelines, including each pipeline's current run. Run state is reconciled with the provider before reading (staleness-gated), so polling this endpoint observes runs reaching a terminal status. Responses never include usage data.","tags":["Pipelines"],"operationId":"listPipelines","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted pipeline record, in ready or failed status.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"runtime":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentRun":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","running","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","status","lifecycle","regionPreference","runtime","repo","currentRun","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a pipeline","description":"Provisions a pipeline synchronously (template repo plus provider project) and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. Requires an API key whose role includes developer. Pass an idempotencyKey to make retries safe: repeating a create with the same key returns the existing pipeline instead of provisioning a duplicate. Initial environment variables are applied to the provider project before the first run. Omit regionPreference to use the default region.","tags":["Pipelines"],"operationId":"createPipeline","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"regionPreference":{"nullable":true,"type":"string","minLength":1},"environmentVariables":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"value":{"type":"string","maxLength":65536}},"required":["key","value"]}},"database":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["existing"]},"databaseId":{"type":"string","minLength":1}},"required":["mode","databaseId"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["create"]},"displayName":{"type":"string","minLength":1,"maxLength":63},"regionId":{"type":"string","minLength":1}},"required":["mode","displayName","regionId"],"additionalProperties":false}]},"redis":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["existing"]},"databaseId":{"type":"string","minLength":1}},"required":["mode","databaseId"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["create"]},"displayName":{"type":"string","minLength":1,"maxLength":63},"primaryRegion":{"type":"string","minLength":1}},"required":["mode","displayName","primaryRegion"],"additionalProperties":false}]},"idempotencyKey":{"type":"string","minLength":1,"maxLength":255}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}":{"get":{"responses":{"200":{"description":"The pipeline record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"slug":{"type":"string"},"provider":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"lifecycle":{"type":"string","enum":["active","suspended"]},"regionPreference":{"nullable":true,"type":"string"},"runtime":{"nullable":true,"type":"string"},"repo":{"nullable":true,"type":"object","properties":{"provider":{"type":"string"},"owner":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"url":{"nullable":true,"type":"string"},"defaultBranch":{"nullable":true,"type":"string"},"subdirectory":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"clone":{"type":"object","properties":{"httpsUrl":{"type":"string"},"sshUrl":{"type":"string"},"cliCommand":{"type":"string"}},"required":["httpsUrl","sshUrl","cliCommand"],"additionalProperties":false}},"required":["provider","owner","name","fullName","url","defaultBranch","subdirectory","status","clone"],"additionalProperties":false},"currentRun":{"nullable":true,"type":"object","properties":{"status":{"type":"string","enum":["queued","running","ready","failed","canceled"]},"target":{"type":"string","enum":["production","preview","staging"]},"url":{"nullable":true,"type":"string"},"gitProvider":{"nullable":true,"type":"string"},"gitRepo":{"nullable":true,"type":"string"},"gitBranch":{"nullable":true,"type":"string"},"gitCommitSha":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["status","target","url","gitProvider","gitRepo","gitBranch","gitCommitSha","createdAt","readyAt","failedAt","failureState"],"additionalProperties":false},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","slug","provider","status","lifecycle","regionPreference","runtime","repo","currentRun","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a pipeline","description":"Returns one pipeline's record, including its current run and failure state when present. Run state is reconciled with the provider before reading (staleness-gated), so poll this endpoint after a create or redeploy to observe the run reaching a terminal status. Responses never include usage data.","tags":["Pipelines"],"operationId":"getPipeline","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The pipeline was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The pipeline is protected from deletion (code resource_protected). Remove protection from the dashboard before deleting via the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Delete a pipeline","description":"Tears down the provider project and repo, then soft-deletes the record — the same semantics as deleting from the dashboard. Requires an API key whose role includes developer. Deletion is permanent. Returns 403 resource_protected when the pipeline has deletion protection enabled; protection can only be removed from the dashboard.","tags":["Pipelines"],"operationId":"deletePipeline","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/tags":{"put":{"responses":{"200":{"description":"The stored tag list, after normalization.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set a pipeline's tags","description":"Replaces a pipeline's tag list with the submitted list and returns the stored list. Tags are normalized to lowercase slugs, so `Live` is stored as `live`. Send an empty list to remove every tag. Requires an API key whose role is owner, admin, or developer.","tags":["Pipelines"],"operationId":"setPipelineTags","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}},"required":["tags"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/redeploy":{"post":{"responses":{"202":{"description":"The new run's initial status.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["queued","running","ready","failed","canceled"]}},"required":["status"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Redeploy a pipeline","description":"Triggers a fresh production run from the tip of the pipeline's linked branch and responds 202 with the new run's initial status (queued or running). Poll the pipeline detail endpoint to observe the run reaching a terminal status. The new run picks up the currently-applied environment variables.","tags":["Pipelines"],"operationId":"redeployPipeline","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/setup":{"post":{"responses":{"202":{"description":"The new run, in status running.","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["app_setup"]},"resourceKind":{"type":"string","enum":["apps","pipelines"]},"resourceId":{"type":"string"},"status":{"type":"string","enum":["running","ready","needs_person","failed","expired"]},"reason":{"nullable":true,"type":"string"},"pullRequestUrl":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"agentUrl":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"finishedAt":{"nullable":true,"type":"string"}},"required":["id","kind","resourceKind","resourceId","status","reason","pullRequestUrl","liveUrl","agentUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["run"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"503":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Start pipeline setup","description":"Starts a cloud setup run for the pipeline: Khotan launches a cloud agent against the organization's repository with the intake, gives it a short-lived developer key, and merges its setup PR when every guard holds. Responds 202 with the run in status running; poll GET /api/v1/pipelines/{pipelineId}/setup to follow it. Requires an API key whose role includes developer, created by a member. Only one run may be running per organization. Pass an idempotencyKey to make retries safe.","tags":["Pipelines"],"operationId":"startPipelineSetup","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":20,"pattern":"^[A-Za-z0-9][A-Za-z0-9 &.-]*$"},"website":{"type":"string","format":"uri"},"logo":{"nullable":true,"type":"string","format":"uri"},"idempotencyKey":{"description":"Repeat a start with the same key to get the same run back instead of starting a second one.","type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"get":{"responses":{"200":{"description":"The newest run, or null.","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["app_setup"]},"resourceKind":{"type":"string","enum":["apps","pipelines"]},"resourceId":{"type":"string"},"status":{"type":"string","enum":["running","ready","needs_person","failed","expired"]},"reason":{"nullable":true,"type":"string"},"pullRequestUrl":{"nullable":true,"type":"string"},"liveUrl":{"nullable":true,"type":"string"},"agentUrl":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"finishedAt":{"nullable":true,"type":"string"}},"required":["id","kind","resourceKind","resourceId","status","reason","pullRequestUrl","liveUrl","agentUrl","createdAt","finishedAt"],"additionalProperties":false}},"required":["run"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get pipeline setup","description":"Returns the pipeline's newest setup run, with its status, the reason for any status other than ready, the setup PR, and the live URL once it is ready. Returns { run: null } when setup never ran through Khotan.","tags":["Pipelines"],"operationId":"getPipelineSetup","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/setup/pull-request":{"post":{"responses":{"200":{"description":"The setup PR, and whether it merged.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"number":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"merged":{"type":"boolean"},"mergeCommitSha":{"nullable":true,"type":"string"}},"required":["url","number","merged","mergeCommitSha"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Open or merge the pipeline's setup PR","description":"Opens the setup PR from the pipeline's cursor/setup-<slug> branch into the default branch, or updates the open one, through Khotan's GitHub app. The setup script calls it when gh cannot. With merge: true, Khotan merges with a merge commit only when the caller is the running setup run's own key, the PR changes only the pipeline, its database package, bun.lock, khotan.bindings.json, and skills-lock.json, and every check on the head commit passed. A refused merge returns 409 with the guard's code and leaves the PR open.","tags":["Pipelines"],"operationId":"upsertPipelineSetupPullRequest","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":256},"body":{"type":"string","maxLength":65536},"merge":{"description":"Merge the PR when every guard holds. Only the running setup run's own key can merge.","type":"boolean"}},"required":["title","body"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/source":{"get":{"responses":{"200":{"description":"The pipeline's source files.","content":{"application/json":{"schema":{"type":"object","properties":{"subdirectory":{"nullable":true,"type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"encoding":{"type":"string","enum":["utf-8","base64"]},"content":{"type":"string"}},"required":["path","encoding","content"],"additionalProperties":false}}},"required":["subdirectory","files"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The pipeline exists but its source repository is not ready yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The source is too large to download as a single response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Download a pipeline's source","description":"Returns the pipeline's source files (its subdirectory inside the org mono-root repo) as a JSON listing with paths rooted at the pipeline. Text files are returned as utf-8, binary files as base64. Responds 409 source_not_ready when the pipeline has no repo yet, and 413 source_too_large for sources past the inline download limit.","tags":["Pipelines"],"operationId":"getPipelineSource","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/env":{"get":{"responses":{"200":{"description":"Environment variable metadata, never values.","content":{"application/json":{"schema":{"type":"object","properties":{"variables":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"target":{"type":"array","items":{"type":"string","enum":["production","preview","development"]}},"type":{"type":"string"},"updatedAt":{"nullable":true,"type":"string"},"differsAcrossTargets":{"type":"boolean"}},"required":["key","target","type","updatedAt","differsAcrossTargets"],"additionalProperties":false}}},"required":["variables"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"List environment variables","description":"Returns environment variable metadata (key, targets, type, last update) for the pipeline's provider project. Requires an API key whose role includes developer. Values are never included; use the reveal endpoint to fetch one decrypted value.","tags":["Pipelines"],"operationId":"listPipelineEnvVars","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"200":{"description":"The partial result: which keys were upserted and which failed.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}}},"required":["upsertedKeys","failedKeys"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Bulk upsert environment variables","description":"Creates or updates many environment variables for the given environments (defaults to all when target is omitted) in one request; other environments keep their value. Requires an API key whose role includes developer. Writes are not transactional: the response reports upsertedKeys and failedKeys, and earlier successful writes remain applied — retry the failed subset. Changes apply on the next run; trigger a redeploy to start one.","tags":["Pipelines"],"operationId":"bulkUpsertPipelineEnvVars","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"variables":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"},"value":{"type":"string","maxLength":65536}},"required":["key","value"]}},"target":{"minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["production","preview","development"]}}},"required":["variables"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/env/{key}":{"get":{"responses":{"200":{"description":"The decrypted value.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}},"required":["key","value"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Reveal an environment variable","description":"Returns the decrypted value of one environment variable for a single environment (defaults to production; pass ?target=preview|development). Treat the value as a secret. Every reveal is audit-logged with the key name and caller; the value itself is never logged.","tags":["Pipelines"],"operationId":"revealPipelineEnvVar","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"}},{"name":"target","in":"query","required":false,"schema":{"type":"string","enum":["production","preview","development"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"put":{"responses":{"200":{"description":"The variable was written.","content":{"application/json":{"schema":{"type":"object","properties":{"applied":{"type":"boolean","enum":[true]}},"required":["applied"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Upsert an environment variable","description":"Creates or updates one environment variable for the given environments (defaults to all when target is omitted); other environments keep their value. Changes apply on the next run; trigger a redeploy to start one.","tags":["Pipelines"],"operationId":"upsertPipelineEnvVar","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","maxLength":65536},"target":{"minItems":1,"maxItems":3,"type":"array","items":{"type":"string","enum":["production","preview","development"]}}},"required":["value"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The variable was removed.","content":{"application/json":{"schema":{"type":"object","properties":{"applied":{"type":"boolean","enum":[true]}},"required":["applied"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete an environment variable","description":"Removes one environment variable's value from the given environment (pass ?target=production|preview|development; omit to remove from all). Deleting a value that does not exist succeeds. Changes apply on the next run; trigger a redeploy to start one.","tags":["Pipelines"],"operationId":"deletePipelineEnvVar","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"key","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[A-Za-z_][A-Za-z0-9_]*$"}},{"name":"target","in":"query","required":false,"schema":{"type":"string","enum":["production","preview","development"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/email-subscriptions":{"get":{"responses":{"200":{"description":"The pipeline's email subscriptions.","content":{"application/json":{"schema":{"type":"object","properties":{"subscriptions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"emailIntegrationId":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"target":{"type":"object","properties":{"type":{"type":"string","enum":["app","pipeline"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"deliveryUrl":{"type":"string"},"status":{"type":"string","enum":["pending","active","failed","disabled"]},"expiresAt":{"nullable":true,"type":"string"},"failure":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","emailIntegrationId","provider","target","deliveryUrl","status","expiresAt","failure","createdAt","updatedAt"],"additionalProperties":false}}},"required":["subscriptions"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List a pipeline's email subscriptions","description":"Lists this pipeline's mailbox subscriptions. Each delivers new-message notifications from a connected Gmail or Outlook mailbox directly to its delivery URL. Responses never include the Outlook client state.","tags":["Pipelines"],"operationId":"listPipelineEmailSubscriptions","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created subscription. For Outlook it includes the clientState secret.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"emailIntegrationId":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"target":{"type":"object","properties":{"type":{"type":"string","enum":["app","pipeline"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"deliveryUrl":{"type":"string"},"status":{"type":"string","enum":["pending","active","failed","disabled"]},"expiresAt":{"nullable":true,"type":"string"},"failure":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"clientState":{"nullable":true,"type":"string"}},"required":["id","emailIntegrationId","provider","target","deliveryUrl","status","expiresAt","failure","createdAt","updatedAt","clientState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Subscribe a pipeline to a mailbox","description":"Subscribes this pipeline to a connected Gmail or Outlook mailbox so the provider delivers new-message notifications directly to deliveryUrl (https). Gmail: Khotan registers the mailbox watch and a Pub/Sub push subscription whose pushes carry a Google OIDC token. Outlook: Khotan creates a Microsoft Graph subscription — Graph first validates deliveryUrl by POSTing a validationToken query parameter that the receiver must echo as text/plain within 10 seconds, and every later notification carries the clientState returned here. Secret-bearing and never cached; recover a lost clientState from the client-state endpoint.","tags":["Pipelines"],"operationId":"createPipelineEmailSubscription","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emailIntegrationId":{"type":"string","minLength":1},"deliveryUrl":{"type":"string","format":"uri"}},"required":["emailIntegrationId","deliveryUrl"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/email-subscriptions/{subscriptionId}":{"get":{"responses":{"200":{"description":"The subscription.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"emailIntegrationId":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"target":{"type":"object","properties":{"type":{"type":"string","enum":["app","pipeline"]},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false},"deliveryUrl":{"type":"string"},"status":{"type":"string","enum":["pending","active","failed","disabled"]},"expiresAt":{"nullable":true,"type":"string"},"failure":{"nullable":true,"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","emailIntegrationId","provider","target","deliveryUrl","status","expiresAt","failure","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a pipeline's email subscription","tags":["Pipelines"],"operationId":"getPipelineEmailSubscription","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The subscription was removed.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Unsubscribe a pipeline from a mailbox","description":"Stops delivery to this pipeline. Gmail: deletes the Pub/Sub push subscription and, when nothing else subscribes to that mailbox, stops the Gmail watch and removes the topic. Outlook: deletes the Microsoft Graph subscription (already-missing counts as deleted); returns 502 provider_error and keeps the subscription if Graph refuses, so the call can be retried.","tags":["Pipelines"],"operationId":"deletePipelineEmailSubscription","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/email-subscriptions/{subscriptionId}/client-state":{"get":{"responses":{"200":{"description":"The subscription's client state. Secret-bearing.","content":{"application/json":{"schema":{"type":"object","properties":{"subscriptionId":{"type":"string"},"clientState":{"type":"string"}},"required":["subscriptionId","clientState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a pipeline's Outlook subscription client state (secret)","description":"Returns the Outlook subscription's clientState — the secret Microsoft Graph sends with every notification to the delivery URL. Compare it in constant time and reject notifications that don't match. Treat it as a secret. Returns 400 client_state_unsupported for Gmail subscriptions, which are verified by Pub/Sub OIDC instead.","tags":["Pipelines"],"operationId":"getPipelineEmailSubscriptionClientState","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/oauth":{"get":{"responses":{"200":{"description":"The OAuth client status.","content":{"application/json":{"schema":{"type":"object","properties":{"client":{"nullable":true,"type":"object","properties":{"clientId":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"extraRedirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"callbackPath":{"type":"string"},"createdAt":{"type":"string"}},"required":["clientId","issuer","redirectUris","extraRedirectUris","callbackPath","createdAt"],"additionalProperties":false},"canGenerate":{"type":"boolean"},"unavailableReason":{"nullable":true,"type":"string","enum":["no_provider_project","no_primary_hostname"]}},"required":["client","canGenerate","unavailableReason"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get pipeline OAuth status","description":"Returns non-secret OAuth client status, effective redirect URIs, and setup availability.","tags":["Pipelines"],"operationId":"getPipelineOAuthStatus","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The OAuth client was disconnected.","content":{"application/json":{"schema":{"type":"object","properties":{"disconnected":{"type":"boolean"}},"required":["disconnected"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Disconnect pipeline OAuth","description":"Revokes the OAuth client and removes its injected environment variables.","tags":["Pipelines"],"operationId":"disconnectPipelineOAuth","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/oauth/setup":{"post":{"responses":{"200":{"description":"The new OAuth credentials.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"},"outcome":{"type":"string","enum":["created","rotated"]},"clientId":{"type":"string"},"clientSecret":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["upsertedKeys","failedKeys","requiresRedeploy","outcome","clientId","clientSecret","issuer","redirectUris"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set up pipeline OAuth","description":"Creates the OAuth client once and returns its plaintext secret. Returns a conflict when credentials already exist.","tags":["Pipelines"],"operationId":"setupPipelineOAuth","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/oauth/sync":{"post":{"responses":{"200":{"description":"The synchronized OAuth configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"},"outcome":{"type":"string","enum":["synced"]},"clientId":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["upsertedKeys","failedKeys","requiresRedeploy","outcome","clientId","issuer","redirectUris"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Sync pipeline OAuth","description":"Repairs redirect and environment configuration without rotating the client secret.","tags":["Pipelines"],"operationId":"syncPipelineOAuth","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/oauth/rotate":{"post":{"responses":{"200":{"description":"The rotated credentials and redeploy result.","content":{"application/json":{"schema":{"type":"object","properties":{"upsertedKeys":{"type":"array","items":{"type":"string"}},"failedKeys":{"type":"array","items":{"type":"string"}},"requiresRedeploy":{"type":"boolean"},"outcome":{"type":"string","enum":["rotated"]},"clientId":{"type":"string"},"clientSecret":{"type":"string"},"issuer":{"type":"string","format":"uri"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"redeploy":{"type":"object","properties":{"requested":{"type":"boolean"},"started":{"type":"boolean"},"status":{"nullable":true,"type":"string","enum":["queued","building","running","ready","failed","canceled"]},"error":{"nullable":true,"type":"string"}},"required":["requested","started","status","error"],"additionalProperties":false}},"required":["upsertedKeys","failedKeys","requiresRedeploy","outcome","clientId","clientSecret","issuer","redirectUris","redeploy"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rotate pipeline OAuth secret","description":"Invalidates the current secret, injects a new one, and optionally starts a fresh deployment.","tags":["Pipelines"],"operationId":"rotatePipelineOAuth","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redeploy":{"default":false,"type":"boolean"}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/oauth/redirect-uris":{"put":{"responses":{"200":{"description":"The effective redirect URI set.","content":{"application/json":{"schema":{"type":"object","properties":{"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["redirectUris"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Replace pipeline OAuth redirect URIs","description":"Replaces operator-managed redirect URIs while preserving the managed production callback.","tags":["Pipelines"],"operationId":"setPipelineOAuthRedirectUris","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redirectUris":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","maxLength":2048,"format":"uri"}}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/pipelines/{pipelineId}/oauth/export":{"post":{"responses":{"200":{"description":"The .env.local content.","content":{"application/json":{"schema":{"type":"string"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Export pipeline OAuth environment","description":"Returns secret-bearing .env.local content for the existing OAuth client.","tags":["Pipelines"],"operationId":"exportPipelineOAuth","parameters":[{"name":"pipelineId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files":{"get":{"responses":{"200":{"description":"One page of the organization's files.","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"contentUrl":{"type":"string","description":"Stable authenticated API URL for opening the file content. It redirects to a short-lived inline storage URL when the file is ready."},"folderId":{"nullable":true,"description":"The containing folder's id, or null for the root.","type":"string"},"path":{"type":"string","description":"The file's full display path, including the file name. Paths change on rename/move; store ids."},"kind":{"type":"string","enum":["archive","audio","document","image","other","pdf","spreadsheet","video"]},"extension":{"nullable":true,"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["pending","ready"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","contentUrl","folderId","path","kind","extension","contentType","sizeBytes","status","createdAt","updatedAt"],"additionalProperties":false}},"nextCursor":{"nullable":true,"description":"Opaque cursor for the next page; null when no more results.","type":"string"}},"required":["files","nextCursor"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List files","description":"Returns the organization's non-deleted files ordered newest first, with cursor pagination (default 50 per page, max 200). By default only ready files appear; pass status=pending to list incomplete uploads that have not passed their upload expiry window (cancel them with DELETE /api/v1/files/{fileId}). Scope the list with folderId or path (mutually exclusive); path=/ means the root folder, and omitting both lists every folder. Each file includes contentUrl, a stable authenticated API URL for opening the file content.","tags":["Files"],"operationId":"listFiles","parameters":[{"name":"folderId","in":"query","required":false,"schema":{"type":"string","minLength":1}},{"name":"path","in":"query","required":false,"schema":{"type":"string","maxLength":4096,"description":"A folder path such as \"/invoices/2026\". Segments match active folders case-insensitively; \"/\" means the root."}},{"name":"status","in":"query","required":false,"schema":{"default":"ready","description":"File status filter. Default `ready`. Use `pending` to list incomplete uploads that have not passed their upload expiry window; cancel them with DELETE /api/v1/files/{fileId}.","type":"string","enum":["ready","pending"]}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files/{fileId}":{"get":{"responses":{"200":{"description":"The file record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"contentUrl":{"type":"string","description":"Stable authenticated API URL for opening the file content. It redirects to a short-lived inline storage URL when the file is ready."},"folderId":{"nullable":true,"description":"The containing folder's id, or null for the root.","type":"string"},"path":{"type":"string","description":"The file's full display path, including the file name. Paths change on rename/move; store ids."},"kind":{"type":"string","enum":["archive","audio","document","image","other","pdf","spreadsheet","video"]},"extension":{"nullable":true,"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["pending","ready"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","contentUrl","folderId","path","kind","extension","contentType","sizeBytes","status","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a file","description":"Returns the file's record, including its folder id, resolved path, and stable authenticated contentUrl. Pending (prepared but not yet completed) files are visible here with status pending; deleted files respond 404.","tags":["Files"],"operationId":"getFile","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"patch":{"responses":{"200":{"description":"The updated file record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"contentUrl":{"type":"string","description":"Stable authenticated API URL for opening the file content. It redirects to a short-lived inline storage URL when the file is ready."},"folderId":{"nullable":true,"description":"The containing folder's id, or null for the root.","type":"string"},"path":{"type":"string","description":"The file's full display path, including the file name. Paths change on rename/move; store ids."},"kind":{"type":"string","enum":["archive","audio","document","image","other","pdf","spreadsheet","video"]},"extension":{"nullable":true,"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["pending","ready"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","contentUrl","folderId","path","kind","extension","contentType","sizeBytes","status","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rename or move a file","description":"Renames the file (name) and/or moves it (folderId or folderPath, mutually exclusive; null folderId or path / targets the root). When both a rename and a move are requested they apply in sequence — rename, then move — and a move failure leaves the rename applied. A folderPath creates any missing folders along the path (mkdir -p), matching upload prepare.","tags":["Files"],"operationId":"updateFile","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"folderId":{"description":"Destination folder id; null moves the file to the root.","nullable":true,"type":"string","minLength":1},"folderPath":{"description":"Destination folder path; \"/\" moves the file to the root. Missing folders along the path are created (\"mkdir -p\" semantics), matching upload prepare.","type":"string","maxLength":4096}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The file was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a file","description":"Soft-deletes the file and removes its bytes from storage. Works for ready files and pending uploads (cancel an incomplete upload by deleting it). The file disappears from listings immediately; deletion is permanent.","tags":["Files"],"operationId":"deleteFile","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files/{fileId}/content":{"get":{"responses":{"302":{"description":"Redirects to the short-lived inline storage URL."},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Open file content","description":"Authenticates the caller, verifies the file belongs to the caller's organization and is ready, then redirects to a short-lived inline storage URL. File records expose this path as contentUrl; send the same API key when requesting it.","tags":["Files"],"operationId":"openFileContent","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/files/{fileId}/preview":{"get":{"responses":{"200":{"description":"The presigned preview URL and its expiry.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Short-lived presigned GET URL with inline disposition for rendering in-browser."},"expiresAt":{"type":"string"}},"required":["url","expiresAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a preview URL","description":"Returns a short-lived presigned URL (inline disposition) for a ready, preview-eligible file (images and PDFs); load the bytes directly from that URL before it expires. The URL grants access to the file contents — treat it as a secret and request a fresh one per render. Responds 409 until the file is ready, and 415 when the file cannot be previewed inline.","tags":["Files"],"operationId":"getFilePreviewUrl","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/folders":{"get":{"responses":{"200":{"description":"The organization's active folders.","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parentId":{"nullable":true,"description":"The parent folder's id, or null for a top-level folder.","type":"string"},"path":{"type":"string","description":"The folder's full path. Paths change on rename; store ids."}},"required":["id","name","parentId","path"],"additionalProperties":false}}},"required":["folders"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List folders","description":"Returns every active folder in the organization as a flat list with id, name, parent id, and path. Reconstruct the hierarchy from parentId, or address folders by path on the file endpoints.","tags":["Folders"],"operationId":"listFolders","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created folder.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parentId":{"nullable":true,"description":"The parent folder's id, or null for a top-level folder.","type":"string"},"path":{"type":"string","description":"The folder's full path. Paths change on rename; store ids."}},"required":["id","name","parentId","path"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a folder","description":"Creates a folder under the root, a parentId, or a parentPath (mutually exclusive; the parent must already exist). Sibling folder names are unique case-insensitively — a duplicate name responds 409.","tags":["Folders"],"operationId":"createFolder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"parentId":{"nullable":true,"type":"string","minLength":1},"parentPath":{"description":"Parent folder path; \"/\" or omitted creates under the root.","nullable":true,"type":"string","maxLength":4096}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/folders/{folderId}":{"patch":{"responses":{"200":{"description":"The renamed folder.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parentId":{"nullable":true,"description":"The parent folder's id, or null for a top-level folder.","type":"string"},"path":{"type":"string","description":"The folder's full path. Paths change on rename; store ids."}},"required":["id","name","parentId","path"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rename a folder","description":"Renames the folder. Descendant folder paths update automatically; previously stored paths into the subtree stop resolving, so store folder ids rather than paths. Sibling name conflicts respond 409.","tags":["Folders"],"operationId":"renameFolder","parameters":[{"name":"folderId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The folder was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a folder","description":"Soft-deletes an empty folder. A folder containing active child folders, ready files, or live pending uploads responds 409 with code folder_not_empty; when live pending uploads block deletion, error.details.pendingFileIds lists those file ids. Pending uploads past their upload expiry window do not block deletion. Move, cancel, or wait for expiry, then retry.","tags":["Folders"],"operationId":"deleteFolder","parameters":[{"name":"folderId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/ai-capture/unattributed":{"get":{"responses":{"200":{"description":"Parked remotes this organization may claim.","content":{"application/json":{"schema":{"type":"object","properties":{"remotes":{"type":"array","items":{"type":"object","properties":{"remote":{"nullable":true,"type":"string"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991},"firstSeenAt":{"type":"string"}},"required":["remote","count","firstSeenAt"],"additionalProperties":false}}},"required":["remotes"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List unattributed capture remotes","description":"Returns parked remotes this organization already owns and may claim.","tags":["AI capture"],"operationId":"listUnattributedCaptureRemotes","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/ai-capture/assignments":{"post":{"responses":{"200":{"description":"The remote was assigned and parked records were backfilled.","content":{"application/json":{"schema":{"type":"object","properties":{"organizationId":{"type":"string"},"projectId":{"type":"string"},"backfilled":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["organizationId","projectId","backfilled"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Assign a capture remote to this organization","description":"Maps a git remote this organization already owns to the caller and backfills parked records for that remote.","tags":["AI capture"],"operationId":"assignCaptureRemote","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"remote":{"type":"string","minLength":1,"maxLength":2048},"displayName":{"type":"string","minLength":1,"maxLength":256}},"required":["remote"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/traces/search":{"get":{"responses":{"200":{"description":"Ranked passages, most relevant first.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"threadId":{"type":"string","description":"The trace thread. Open it on the dashboard at /data/implementation/{threadId}."},"title":{"type":"string","description":"The thread's first request, cut to 200 characters."},"projectName":{"type":"string"},"tool":{"nullable":true,"description":"The coding tool: cursor, claude, codex, or devin.","type":"string"},"branch":{"nullable":true,"type":"string"},"actor":{"nullable":true,"description":"Who ran the session: the member's email, else the device label.","type":"string"},"startedDay":{"nullable":true,"description":"The day the thread started, as YYYY-MM-DD.","type":"string"},"chunkIndex":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Position of the passage within the thread."},"content":{"type":"string","description":"The matching passage: user requests and assistant replies."},"score":{"type":"number","description":"Relevance score; higher is more relevant. The reranker's score from 0 to 1, or the Reciprocal Rank Fusion score when reranking is unavailable."}},"required":["threadId","title","projectName","tool","branch","actor","startedDay","chunkIndex","content","score"],"additionalProperties":false}}},"required":["results"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Search implementation traces","description":"Hybrid semantic + full-text search over the organization's implementation traces, reranked for relevance. Returns the best passage from each matching conversation, with its thread id, title, project, tool, branch, actor, start day, and a relevance score. Requires the developer role.","tags":["Implementation traces"],"operationId":"searchImplementationTraces","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":500,"description":"Natural-language search query. Retrieval is hybrid: semantic vector similarity over trace passages combined with full-text search, then reranked."}},{"name":"k","in":"query","required":false,"schema":{"default":10,"description":"Maximum number of results to return (1-50; default 10).","type":"integer","minimum":1,"maximum":50}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context":{"get":{"responses":{"200":{"description":"The document manifest.","content":{"application/json":{"schema":{"type":"object","properties":{"documents":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"nullable":true,"description":"One-line purpose statement for selection without fetching.","type":"string"},"kind":{"type":"string","enum":["instructions","knowledge","record"]},"tags":{"type":"array","items":{"type":"string"}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"contentSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Raw markdown size in bytes."},"folderId":{"nullable":true,"description":"Folder placement; null at the organization root.","type":"string"},"path":{"nullable":true,"description":"Human-readable folder path; null at the organization root.","type":"string"},"updatedAt":{"type":"string"},"staffOnly":{"type":"boolean","description":"Visible only to the operator's team. The organization's own members and keys never see it."}},"required":["slug","title","description","kind","tags","revision","contentSize","folderId","path","updatedAt","staffOnly"],"additionalProperties":false}}},"required":["documents"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List context documents","description":"Returns the organization's active document manifest: slug, title, description, kind, tags, revision, contentSize, and updatedAt — never content. Filter by kind, or pass q to search via the weighted full-text index (title ranks above description above content). Fetch individual documents by slug for their markdown.","tags":["Context"],"operationId":"listContextDocuments","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["instructions","knowledge","record"],"description":"\"instructions\" = procedures to follow (SOPs, runbooks); \"knowledge\" = reference material (specs, policies); \"record\" = accounts of events (meeting notes, transcripts, interviews)."}},{"name":"q","in":"query","required":false,"schema":{"description":"Full-text search across title, description, and content, ranked title > description > content.","type":"string","minLength":1,"maxLength":500}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created document, including its content.","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"nullable":true,"description":"One-line purpose statement for selection without fetching.","type":"string"},"kind":{"type":"string","enum":["instructions","knowledge","record"]},"tags":{"type":"array","items":{"type":"string"}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"contentSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Raw markdown size in bytes."},"folderId":{"nullable":true,"description":"Folder placement; null at the organization root.","type":"string"},"path":{"nullable":true,"description":"Human-readable folder path; null at the organization root.","type":"string"},"updatedAt":{"type":"string"},"staffOnly":{"type":"boolean","description":"Visible only to the operator's team. The organization's own members and keys never see it."},"content":{"type":"string","description":"The canonical raw markdown."},"properties":{"type":"object","additionalProperties":{},"description":"Structured facts beside the markdown: any JSON object."},"links":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"],"description":"The kind of project or resource the document links to."},"id":{"type":"string","description":"The linked project or resource id."},"name":{"type":"string","description":"The target's current name, or \"Deleted\" when it no longer exists."},"removed":{"type":"boolean","description":"True when the target was deleted. Replace or remove the link."}},"required":["type","id","name","removed"],"additionalProperties":false},"description":"Projects and resources this document links to."},"createdAt":{"type":"string"},"createdByType":{"type":"string","enum":["agent","api","user"]},"updatedByType":{"type":"string","enum":["agent","api","user"]}},"required":["slug","title","description","kind","tags","revision","contentSize","folderId","path","updatedAt","staffOnly","content","properties","links","createdAt","createdByType","updatedByType"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a context document","description":"Creates a markdown document from title, kind, and content, with optional slug, description, and tags. The slug is derived from the title when omitted and is immutable after creation; a collision with an active document responds 409. Limit violations respond 422 with the limit name, maximum, and submitted value.","tags":["Context"],"operationId":"createContextDocument","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":300},"kind":{"type":"string","enum":["instructions","knowledge","record"],"description":"\"instructions\" = procedures to follow (SOPs, runbooks); \"knowledge\" = reference material (specs, policies); \"record\" = accounts of events (meeting notes, transcripts, interviews)."},"content":{"type":"string","description":"Canonical raw markdown (max 256 KB, enforced by the domain layer)."},"slug":{"description":"Derived from the title when omitted; conflicts return 409.","type":"string","minLength":1,"maxLength":120,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"description":{"type":"string","description":"One-line purpose statement powering the manifest listing (max 200 characters, enforced by the domain layer)."},"tags":{"maxItems":32,"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"description":"Flat tag list; express hierarchy with naming conventions like \"ops/runbook/deploy\"."},"properties":{"type":"object","additionalProperties":{},"description":"Structured facts beside the markdown: any JSON object."},"folderId":{"description":"Folder placement; omit or null for the organization root.","nullable":true,"type":"string","minLength":1}},"required":["title","kind","content"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/search":{"get":{"responses":{"200":{"description":"Ranked snippets, most relevant first.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string","description":"Source document slug; fetch its full markdown by slug."},"title":{"type":"string"},"kind":{"type":"string","enum":["instructions","knowledge","record"]},"chunkIndex":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Position of the passage within the document."},"content":{"type":"string","description":"The matching passage text."},"score":{"type":"number","description":"Relevance score; higher is more relevant. The reranker's score from 0 to 1, or the Reciprocal Rank Fusion score when reranking is unavailable."}},"required":["slug","title","kind","chunkIndex","content","score"],"additionalProperties":false}}},"required":["results"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Search context documents","description":"Hybrid semantic + full-text search over the organization's active context documents, reranked for relevance. Returns the best passage from each matching document, with its slug, title, kind, and a relevance score. Use the slug to fetch the full markdown when a passage is insufficient.","tags":["Context"],"operationId":"searchContextDocuments","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":500,"description":"Natural-language search query. Retrieval is hybrid: semantic vector similarity over document passages combined with weighted full-text search, then reranked."}},{"name":"k","in":"query","required":false,"schema":{"default":10,"description":"Maximum number of snippets to return (1-50; default 10).","type":"integer","minimum":1,"maximum":50}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["instructions","knowledge","record"],"description":"\"instructions\" = procedures to follow (SOPs, runbooks); \"knowledge\" = reference material (specs, policies); \"record\" = accounts of events (meeting notes, transcripts, interviews)."}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/folders":{"get":{"responses":{"200":{"description":"The organization's folders.","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"parentId":{"nullable":true,"description":"null at the organization root.","type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"path":{"type":"string","description":"Slash-joined ancestor slugs."},"depth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 at the organization root."},"documentCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Active documents directly in this folder."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","parentId","name","slug","path","depth","documentCount","createdAt","updatedAt"],"additionalProperties":false}}},"required":["folders"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List context folders","description":"Returns the organization's folder tree as a flat list of folders with parentId, name, slug, path, depth, and the active-document count directly within each folder.","tags":["Context"],"operationId":"listContextFolders","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created folder.","content":{"application/json":{"schema":{"type":"object","properties":{"folder":{"type":"object","properties":{"id":{"type":"string"},"parentId":{"nullable":true,"description":"null at the organization root.","type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"path":{"type":"string","description":"Slash-joined ancestor slugs."},"depth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 at the organization root."},"documentCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Active documents directly in this folder."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","parentId","name","slug","path","depth","documentCount","createdAt","updatedAt"],"additionalProperties":false}},"required":["folder"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a context folder","description":"Creates a folder under an optional parent (omit or null for the organization root). The name must be unique among active sibling folders; a collision responds 409 folder_conflict. An unknown parent responds 404 folder_not_found.","tags":["Context"],"operationId":"createContextFolder","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"parentId":{"description":"Parent folder; omit or null for the organization root.","nullable":true,"type":"string","minLength":1}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/folders/{id}":{"patch":{"responses":{"200":{"description":"The updated folder.","content":{"application/json":{"schema":{"type":"object","properties":{"folder":{"type":"object","properties":{"id":{"type":"string"},"parentId":{"nullable":true,"description":"null at the organization root.","type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"path":{"type":"string","description":"Slash-joined ancestor slugs."},"depth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"0 at the organization root."},"documentCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Active documents directly in this folder."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","parentId","name","slug","path","depth","documentCount","createdAt","updatedAt"],"additionalProperties":false}},"required":["folder"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Rename or move a context folder","description":"Renames the folder when name is provided and moves it when parentId is provided (null moves it to the organization root). Sibling name collisions respond 409 folder_conflict; moving a folder into itself or a descendant responds 422 folder_cycle.","tags":["Context"],"operationId":"updateContextFolder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"parentId":{"description":"Pass null to move to the organization root.","nullable":true,"type":"string","minLength":1}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The folder was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a context folder","description":"Soft-deletes the folder. A non-empty folder responds 409 folder_not_empty unless recursive=true is passed, in which case its subfolders and documents are soft-deleted too (their revision history is retained).","tags":["Context"],"operationId":"deleteContextFolder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"recursive","in":"query","required":false,"schema":{"description":"Pass true to delete a non-empty folder and all of its contents.","type":"string","enum":["true","false"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/{slug}":{"get":{"responses":{"200":{"description":"The document record, including content.","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"nullable":true,"description":"One-line purpose statement for selection without fetching.","type":"string"},"kind":{"type":"string","enum":["instructions","knowledge","record"]},"tags":{"type":"array","items":{"type":"string"}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"contentSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Raw markdown size in bytes."},"folderId":{"nullable":true,"description":"Folder placement; null at the organization root.","type":"string"},"path":{"nullable":true,"description":"Human-readable folder path; null at the organization root.","type":"string"},"updatedAt":{"type":"string"},"staffOnly":{"type":"boolean","description":"Visible only to the operator's team. The organization's own members and keys never see it."},"content":{"type":"string","description":"The canonical raw markdown."},"properties":{"type":"object","additionalProperties":{},"description":"Structured facts beside the markdown: any JSON object."},"links":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"],"description":"The kind of project or resource the document links to."},"id":{"type":"string","description":"The linked project or resource id."},"name":{"type":"string","description":"The target's current name, or \"Deleted\" when it no longer exists."},"removed":{"type":"boolean","description":"True when the target was deleted. Replace or remove the link."}},"required":["type","id","name","removed"],"additionalProperties":false},"description":"Projects and resources this document links to."},"createdAt":{"type":"string"},"createdByType":{"type":"string","enum":["agent","api","user"]},"updatedByType":{"type":"string","enum":["agent","api","user"]}},"required":["slug","title","description","kind","tags","revision","contentSize","folderId","path","updatedAt","staffOnly","content","properties","links","createdAt","createdByType","updatedByType"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a context document","description":"Returns the full document, including its raw markdown content, in a JSON envelope. Unknown or deleted slugs respond 404. Fetching a document records its access time.","tags":["Context"],"operationId":"getContextDocument","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"patch":{"responses":{"200":{"description":"The updated document record.","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"nullable":true,"description":"One-line purpose statement for selection without fetching.","type":"string"},"kind":{"type":"string","enum":["instructions","knowledge","record"]},"tags":{"type":"array","items":{"type":"string"}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"contentSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Raw markdown size in bytes."},"folderId":{"nullable":true,"description":"Folder placement; null at the organization root.","type":"string"},"path":{"nullable":true,"description":"Human-readable folder path; null at the organization root.","type":"string"},"updatedAt":{"type":"string"},"staffOnly":{"type":"boolean","description":"Visible only to the operator's team. The organization's own members and keys never see it."},"content":{"type":"string","description":"The canonical raw markdown."},"properties":{"type":"object","additionalProperties":{},"description":"Structured facts beside the markdown: any JSON object."},"links":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"],"description":"The kind of project or resource the document links to."},"id":{"type":"string","description":"The linked project or resource id."},"name":{"type":"string","description":"The target's current name, or \"Deleted\" when it no longer exists."},"removed":{"type":"boolean","description":"True when the target was deleted. Replace or remove the link."}},"required":["type","id","name","removed"],"additionalProperties":false},"description":"Projects and resources this document links to."},"createdAt":{"type":"string"},"createdByType":{"type":"string","enum":["agent","api","user"]},"updatedByType":{"type":"string","enum":["agent","api","user"]}},"required":["slug","title","description","kind","tags","revision","contentSize","folderId","path","updatedAt","staffOnly","content","properties","links","createdAt","createdByType","updatedByType"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Update a context document","description":"Updates the document's title, kind, content, description, tags, properties, and/or folder when expectedRevision matches the current revision. Properties replace the whole object; omit them to keep the current ones. Links are set separately. On mismatch, responds 409 revision_conflict with the current revision and updatedAt. The slug is immutable; title changes never re-slug. Limit violations respond 422 with the limit name, maximum, and submitted value.","tags":["Context"],"operationId":"updateContextDocument","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expectedRevision":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Required compare-and-swap token; a stale value returns 409 with the current revision and updatedAt."},"title":{"type":"string","minLength":1,"maxLength":300},"kind":{"type":"string","enum":["instructions","knowledge","record"],"description":"\"instructions\" = procedures to follow (SOPs, runbooks); \"knowledge\" = reference material (specs, policies); \"record\" = accounts of events (meeting notes, transcripts, interviews)."},"content":{"type":"string","description":"Canonical raw markdown (max 256 KB, enforced by the domain layer)."},"description":{"description":"Pass null to clear the description.","nullable":true,"type":"string"},"tags":{"maxItems":32,"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"description":"Flat tag list; express hierarchy with naming conventions like \"ops/runbook/deploy\"."},"properties":{"type":"object","additionalProperties":{},"description":"Structured facts beside the markdown: any JSON object."},"folderId":{"description":"Move the document; null moves it to the organization root.","nullable":true,"type":"string","minLength":1},"changeSummary":{"description":"Optional note recorded on the revision history entry.","nullable":true,"type":"string","maxLength":500}},"required":["expectedRevision"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The document was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a context document","description":"Soft-deletes the document. It disappears from the manifest and fetches and its slug becomes reusable; the row and its revision history are retained.","tags":["Context"],"operationId":"deleteContextDocument","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/{slug}/raw":{"get":{"responses":{"200":{"description":"The raw markdown body.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a context document's raw markdown","description":"Returns exactly the document's markdown text with a text/markdown content type and no JSON envelope. Unknown or deleted slugs respond 404 (as JSON). Fetching a document records its access time.","tags":["Context"],"operationId":"getContextDocumentRaw","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/{slug}/links":{"get":{"responses":{"200":{"description":"The document's links.","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"],"description":"The kind of project or resource the document links to."},"id":{"type":"string","description":"The linked project or resource id."},"name":{"type":"string","description":"The target's current name, or \"Deleted\" when it no longer exists."},"removed":{"type":"boolean","description":"True when the target was deleted. Replace or remove the link."}},"required":["type","id","name","removed"],"additionalProperties":false}}},"required":["links"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List a context document's links","description":"Returns the projects and resources the document links to, each with its current name. A target that was deleted shows removed: true. Unknown or deleted slugs respond 404.","tags":["Context"],"operationId":"listContextDocumentLinks","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"put":{"responses":{"200":{"description":"The document's links after the change.","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"],"description":"The kind of project or resource the document links to."},"id":{"type":"string","description":"The linked project or resource id."},"name":{"type":"string","description":"The target's current name, or \"Deleted\" when it no longer exists."},"removed":{"type":"boolean","description":"True when the target was deleted. Replace or remove the link."}},"required":["type","id","name","removed"],"additionalProperties":false}}},"required":["links"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set a context document's links","description":"Replaces the document's complete link set with the given projects and resources. Links not listed are removed. Every new target must be live in the organization, or nothing changes. A link whose target was deleted since is kept unless you leave it out. A document can have at most 200 links. Unknown or deleted slugs respond 404.","tags":["Context"],"operationId":"setContextDocumentLinks","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"links":{"maxItems":200,"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"]},"id":{"type":"string","minLength":1,"maxLength":200}},"required":["type","id"]},"description":"The document's complete link set. Links not listed are removed."}},"required":["links"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/{slug}/revisions":{"get":{"responses":{"200":{"description":"The revision history, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"revisions":{"type":"array","items":{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"title":{"type":"string"},"authorType":{"type":"string","enum":["agent","api","user"]},"authorId":{"type":"string"},"changeSummary":{"nullable":true,"description":"Optional note recorded with the revision.","type":"string"},"createdAt":{"type":"string"}},"required":["revision","title","authorType","authorId","changeSummary","createdAt"],"additionalProperties":false}}},"required":["revisions"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List a context document's revisions","description":"Returns the document's revision history, newest first: revision number, title, author, optional change summary, and timestamp — never the snapshot content. Unknown or deleted slugs respond 404.","tags":["Context"],"operationId":"listContextDocumentRevisions","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/{slug}/revisions/{revision}":{"get":{"responses":{"200":{"description":"The revision record, including its snapshot content.","content":{"application/json":{"schema":{"type":"object","properties":{"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"title":{"type":"string"},"authorType":{"type":"string","enum":["agent","api","user"]},"authorId":{"type":"string"},"changeSummary":{"nullable":true,"description":"Optional note recorded with the revision.","type":"string"},"createdAt":{"type":"string"},"content":{"type":"string","description":"The captured markdown snapshot at this revision."},"properties":{"type":"object","additionalProperties":{},"description":"The document properties captured at this revision."}},"required":["revision","title","authorType","authorId","changeSummary","createdAt","content","properties"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a context document revision","description":"Returns a single revision including its captured title and markdown content. Unknown or deleted slugs, and uncaptured revisions, respond 404.","tags":["Context"],"operationId":"getContextDocumentRevision","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"revision","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":9007199254740991}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/context/{slug}/revisions/{revision}/restore":{"post":{"responses":{"200":{"description":"The document at its new, restored revision.","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"nullable":true,"description":"One-line purpose statement for selection without fetching.","type":"string"},"kind":{"type":"string","enum":["instructions","knowledge","record"]},"tags":{"type":"array","items":{"type":"string"}},"revision":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"contentSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Raw markdown size in bytes."},"folderId":{"nullable":true,"description":"Folder placement; null at the organization root.","type":"string"},"path":{"nullable":true,"description":"Human-readable folder path; null at the organization root.","type":"string"},"updatedAt":{"type":"string"},"staffOnly":{"type":"boolean","description":"Visible only to the operator's team. The organization's own members and keys never see it."},"content":{"type":"string","description":"The canonical raw markdown."},"properties":{"type":"object","additionalProperties":{},"description":"Structured facts beside the markdown: any JSON object."},"links":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["project","deployment","pipeline","postgres","redis","turso","r2"],"description":"The kind of project or resource the document links to."},"id":{"type":"string","description":"The linked project or resource id."},"name":{"type":"string","description":"The target's current name, or \"Deleted\" when it no longer exists."},"removed":{"type":"boolean","description":"True when the target was deleted. Replace or remove the link."}},"required":["type","id","name","removed"],"additionalProperties":false},"description":"Projects and resources this document links to."},"createdAt":{"type":"string"},"createdByType":{"type":"string","enum":["agent","api","user"]},"updatedByType":{"type":"string","enum":["agent","api","user"]}},"required":["slug","title","description","kind","tags","revision","contentSize","folderId","path","updatedAt","staffOnly","content","properties","links","createdAt","createdByType","updatedByType"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Restore a context document revision","description":"Restores the document to the given revision by creating a new forward revision whose title and content match that snapshot. Prior revisions are retained. Unknown slugs or uncaptured revisions respond 404; a concurrent write responds 409 revision_conflict.","tags":["Context"],"operationId":"restoreContextDocumentRevision","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"revision","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":9007199254740991}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/integrations/{integrationId}":{"get":{"responses":{"200":{"description":"The integration.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"mailbox":{"type":"string"},"name":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["active","revoked","error"]},"scopes":{"type":"array","items":{"type":"string"}},"hasRefreshToken":{"type":"boolean"},"hasAccessToken":{"type":"boolean"},"accessTokenExpiresAt":{"nullable":true,"type":"string"},"lastSyncedAt":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","provider","mailbox","name","status","scopes","hasRefreshToken","hasAccessToken","accessTokenExpiresAt","lastSyncedAt","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get an integration","description":"Returns one connected mailbox integration. Responses never include token material.","tags":["Integrations"],"operationId":"getIntegration","parameters":[{"name":"integrationId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"patch":{"responses":{"200":{"description":"The updated integration.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["google","microsoft"]},"mailbox":{"type":"string"},"name":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["active","revoked","error"]},"scopes":{"type":"array","items":{"type":"string"}},"hasRefreshToken":{"type":"boolean"},"hasAccessToken":{"type":"boolean"},"accessTokenExpiresAt":{"nullable":true,"type":"string"},"lastSyncedAt":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","provider","mailbox","name","status","scopes","hasRefreshToken","hasAccessToken","accessTokenExpiresAt","lastSyncedAt","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Update an integration","description":"Updates an integration's display name. Pass null to clear it. Tokens and the mailbox are fixed by OAuth and cannot be changed.","tags":["Integrations"],"operationId":"updateIntegration","parameters":[{"name":"integrationId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"nullable":true,"type":"string","maxLength":120}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The integration was disconnected.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Disconnect an integration","description":"Disconnects a mailbox: the stored credentials are revoked and the connection stops driving sync. This is permanent; reconnecting requires a fresh OAuth consent.","tags":["Integrations"],"operationId":"deleteIntegration","parameters":[{"name":"integrationId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects":{"get":{"responses":{"200":{"description":"The organization's projects.","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"nullable":true,"type":"string"},"lifecycle":{"type":"string"},"linearProjectId":{"nullable":true,"type":"string"},"latestSpecRevision":{"type":"number"},"memberCount":{"type":"number"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","slug","name","description","lifecycle","linearProjectId","latestSpecRevision","memberCount","tags","createdAt","updatedAt"],"additionalProperties":false}}},"required":["projects"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List projects","description":"Returns the organization's live projects with their current spec revision and member count. Never includes spec bodies.","tags":["Projects"],"operationId":"listProjects","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created project.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"nullable":true,"type":"string"},"lifecycle":{"type":"string"},"linearProjectId":{"nullable":true,"type":"string"},"latestSpecRevision":{"type":"number"},"memberCount":{"type":"number"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","slug","name","description","lifecycle","linearProjectId","latestSpecRevision","memberCount","tags","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a project","description":"Creates a project and its single root goal node. The slug is derived from the name when omitted and must be unique among the organization's live projects.","tags":["Projects"],"operationId":"createProject","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"slug":{"description":"Derived from the name when omitted; conflicts return 409.","type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"description":{"nullable":true,"type":"string","maxLength":2000}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/linear-options":{"get":{"responses":{"200":{"description":"Linear projects this workspace can bind to.","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"nullable":true,"type":"string"},"statusName":{"nullable":true,"type":"string"}},"required":["id","name","url","statusName"],"additionalProperties":false}}},"required":["projects"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List Linear projects that can be bound","description":"Returns Linear projects visible to this workspace. Use this to pick a project to bind; a new Khotan project mints its own Linear project on first push.","tags":["Projects"],"operationId":"listLinearProjectOptions","parameters":[{"name":"query","in":"query","required":false,"schema":{"type":"string","maxLength":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}":{"get":{"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"nullable":true,"type":"string"},"lifecycle":{"type":"string"},"linearProjectId":{"nullable":true,"type":"string"},"latestSpecRevision":{"type":"number"},"memberCount":{"type":"number"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","slug","name","description","lifecycle","linearProjectId","latestSpecRevision","memberCount","tags","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a project","description":"Returns one project by id or slug. Cross-organization requests are answered as not found.","tags":["Projects"],"operationId":"getProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"patch":{"responses":{"200":{"description":"The updated project.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"nullable":true,"type":"string"},"lifecycle":{"type":"string"},"linearProjectId":{"nullable":true,"type":"string"},"latestSpecRevision":{"type":"number"},"memberCount":{"type":"number"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","slug","name","description","lifecycle","linearProjectId","latestSpecRevision","memberCount","tags","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Update a project","description":"Updates a project's Khotan-owned attributes. An omitted field, including `tags`, stays unchanged; a sent `tags` list replaces the stored list. The slug is immutable, and execution state is never writable here — the tracker owns it.","tags":["Projects"],"operationId":"updateProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"nullable":true,"type":"string","maxLength":2000},"lifecycle":{"type":"string","enum":["active","archived"]},"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"204":{"description":"The project was deleted.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a project","description":"Soft-deletes a project and frees its slug for reuse. The linked tracker project is retained.","tags":["Projects"],"operationId":"deleteProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/spec":{"get":{"responses":{"200":{"description":"The rendered spec.","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"altitude":{"type":"string"},"revision":{"type":"number"},"root":{"nullable":true,"allOf":[{"$ref":"#/definitions/__schema0"}]},"issuesByPhaseId":{"type":"object","additionalProperties":{"type":"array","items":{}}}},"required":["projectId","altitude","revision","root","issuesByPhaseId"],"additionalProperties":false,"definitions":{"__schema0":{"type":"object","properties":{"id":{"type":"string"},"parentId":{"nullable":true,"type":"string"},"depth":{"type":"number"},"position":{"type":"number"},"title":{"type":"string"},"sectionKey":{"nullable":true,"type":"string"},"documentId":{"nullable":true,"type":"string"},"body":{"nullable":true,"type":"string"},"bodyRevision":{"nullable":true,"type":"number"},"linearMilestoneId":{"nullable":true,"type":"string"},"startDate":{"nullable":true,"type":"string"},"targetDate":{"nullable":true,"type":"string"},"dependsOnNodeId":{"nullable":true,"type":"string"},"children":{"type":"array","items":{"$ref":"#/definitions/__schema0"}}},"required":["id","parentId","depth","position","title","sectionKey","documentId","body","bodyRevision","linearMilestoneId","startDate","targetDate","dependsOnNodeId","children"],"additionalProperties":false}}}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Read a project spec","description":"Returns the project's spec tree limited to the requested altitude: high renders the goal, medium adds phases, low adds items joined with their mirrored tracker issues. Defaults to high.","tags":["Projects"],"operationId":"getProjectSpec","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"altitude","in":"query","required":false,"schema":{"type":"string","enum":["high","medium","low"],"description":"Depth cap over the one spec tree: high renders the goal, medium adds phases, low adds items and their mirrored tracker issues."}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The committed revision.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"revision":{"type":"number"},"changeSummary":{"nullable":true,"type":"string"},"actorType":{"type":"string"},"actorId":{"type":"string"},"committedAt":{"type":"string"},"documentCount":{"type":"number"}},"required":["id","revision","changeSummary","actorType","actorId","committedAt","documentCount"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Commit a spec revision","description":"Applies edits to one or more spec nodes as a single project spec revision. Each edit carries the document revision it was based on, and the request carries the project revision the author last saw; either being stale returns 409.","tags":["Projects"],"operationId":"commitProjectSpecRevision","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"edits":{"minItems":1,"type":"array","items":{"type":"object","properties":{"nodeId":{"type":"string","minLength":1},"title":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","maxLength":1000000},"expectedBodyRevision":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Document revision this edit was based on; a mismatch rejects that node's write rather than overwriting a concurrent edit."}},"required":["nodeId","expectedBodyRevision"]}},"changeSummary":{"nullable":true,"type":"string","maxLength":500},"expectedRevision":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Project spec revision the author last saw; a stale value returns 409 with the current revision."},"links":{"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["cause","effect"]},"kind":{"type":"string","enum":["customer_need","linear_issue","manual","materialization_commit","pull_request"]},"ref":{"type":"string","minLength":1},"url":{"nullable":true,"type":"string","format":"uri"},"label":{"nullable":true,"type":"string","maxLength":200}},"required":["direction","kind","ref"]}}},"required":["edits","expectedRevision"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/spec/nodes":{"post":{"responses":{"201":{"description":"The created node.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"parentId":{"nullable":true,"type":"string"},"depth":{"type":"number"},"position":{"type":"number"},"title":{"type":"string"},"sectionKey":{"nullable":true,"type":"string"},"documentId":{"nullable":true,"type":"string"},"linearMilestoneId":{"nullable":true,"type":"string"},"startDate":{"nullable":true,"type":"string"},"targetDate":{"nullable":true,"type":"string"},"dependsOnNodeId":{"nullable":true,"type":"string"}},"required":["id","parentId","depth","position","title","sectionKey","documentId","linearMilestoneId","startDate","targetDate","dependsOnNodeId"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a spec node","description":"Creates a child under an existing spec node. Use this to add a delivery phase under the Milestones section, or an item under a phase. The thirteen section headings are created with the project and cannot be added here.","tags":["Projects"],"operationId":"createProjectSpecNode","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parentId":{"type":"string","minLength":1},"title":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","maxLength":1000000},"position":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["parentId","title"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/spec/nodes/{nodeId}":{"patch":{"responses":{"200":{"description":"The updated node.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"parentId":{"nullable":true,"type":"string"},"depth":{"type":"number"},"position":{"type":"number"},"title":{"type":"string"},"sectionKey":{"nullable":true,"type":"string"},"documentId":{"nullable":true,"type":"string"},"linearMilestoneId":{"nullable":true,"type":"string"},"startDate":{"nullable":true,"type":"string"},"targetDate":{"nullable":true,"type":"string"},"dependsOnNodeId":{"nullable":true,"type":"string"}},"required":["id","parentId","depth","position","title","sectionKey","documentId","linearMilestoneId","startDate","targetDate","dependsOnNodeId"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Update a spec node","description":"Updates a node's title, sibling position, parent, or milestone plan. A move is applied first so a node that is becoming a phase can then accept dates. Each field is optional; omitted fields are left alone.","tags":["Projects"],"operationId":"updateProjectSpecNode","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"nodeId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"position":{"type":"integer","minimum":0,"maximum":9007199254740991},"parentId":{"description":"Move the node under this parent before applying the other fields.","type":"string","minLength":1},"startDate":{"description":"Milestone plan start. Only valid on a phase.","nullable":true,"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(?:T[\\d:.Z+-]+)?$"},"targetDate":{"description":"Milestone plan target. Only valid on a phase.","nullable":true,"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(?:T[\\d:.Z+-]+)?$"},"dependsOnNodeId":{"description":"Milestone this phase waits on. Only valid on a phase.","nullable":true,"type":"string","minLength":1}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"204":{"description":"The node was deleted.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a spec node","description":"Soft-deletes the node and its descendants. The goal node cannot be deleted — delete the project instead.","tags":["Projects"],"operationId":"deleteProjectSpecNode","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"nodeId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/revisions":{"get":{"responses":{"200":{"description":"The project's committed spec revisions.","content":{"application/json":{"schema":{"type":"object","properties":{"revisions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"revision":{"type":"number"},"changeSummary":{"nullable":true,"type":"string"},"actorType":{"type":"string"},"actorId":{"type":"string"},"committedAt":{"type":"string"},"documentCount":{"type":"number"}},"required":["id","revision","changeSummary","actorType","actorId","committedAt","documentCount"],"additionalProperties":false}}},"required":["revisions"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List spec revisions","description":"Returns the project's committed spec revisions newest-first, with actor, timestamp, change summary, and the number of documents each revision bundled.","tags":["Projects"],"operationId":"listProjectSpecRevisions","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/revisions/{revision}":{"get":{"responses":{"200":{"description":"The revision with its diff and provenance.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"revision":{"type":"number"},"changeSummary":{"nullable":true,"type":"string"},"actorType":{"type":"string"},"actorId":{"type":"string"},"committedAt":{"type":"string"},"documentCount":{"type":"number"},"documents":{"type":"array","items":{"type":"object","properties":{"documentId":{"type":"string"},"nodeId":{"nullable":true,"type":"string"},"title":{"type":"string"},"revision":{"type":"number"},"content":{"type":"string"},"previousContent":{"nullable":true,"type":"string"}},"required":["documentId","nodeId","title","revision","content","previousContent"],"additionalProperties":false}},"links":{"type":"array","items":{"type":"object","properties":{"direction":{"type":"string"},"kind":{"type":"string"},"ref":{"type":"string"},"url":{"nullable":true,"type":"string"},"label":{"nullable":true,"type":"string"}},"required":["direction","kind","ref","url","label"],"additionalProperties":false}}},"required":["id","revision","changeSummary","actorType","actorId","committedAt","documentCount","documents","links"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a spec revision","description":"Returns one committed spec revision: each bundled document's content alongside the previous revision's content, plus the cause and effect provenance edges attached to it.","tags":["Projects"],"operationId":"getProjectSpecRevision","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"revision","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":9007199254740991}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/members":{"get":{"responses":{"200":{"description":"The project's members.","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"resourceKind":{"type":"string"},"resourceId":{"type":"string"},"attachedAt":{"type":"string"}},"required":["id","resourceKind","resourceId","attachedAt"],"additionalProperties":false}}},"required":["members"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List project members","description":"Returns the embedded apps, pipelines, and databases attached to this project.","tags":["Projects"],"operationId":"listProjectMembers","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The created membership.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"resourceKind":{"type":"string"},"resourceId":{"type":"string"},"attachedAt":{"type":"string"}},"required":["id","resourceKind","resourceId","attachedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Attach a resource to a project","description":"Attaches an embedded app, pipeline, or database. A resource already attached elsewhere returns 409 until it is detached.","tags":["Projects"],"operationId":"attachProjectMember","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"resourceKind":{"type":"string","enum":["app","pipeline","database","file"]},"resourceId":{"type":"string","minLength":1}},"required":["resourceKind","resourceId"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"204":{"description":"The membership was removed.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Detach a resource from a project","description":"Removes the membership. The underlying app, pipeline, or database is untouched.","tags":["Projects"],"operationId":"detachProjectMember","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"resourceKind":{"type":"string","enum":["app","pipeline","database","file"]},"resourceId":{"type":"string","minLength":1}},"required":["resourceKind","resourceId"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/metrics/{metricKey}":{"get":{"responses":{"200":{"description":"The metric over the requested range.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string","description":"The name shown: the curated name, else the reported name, else the key."},"displayName":{"nullable":true,"type":"string"},"reportedName":{"nullable":true,"type":"string"},"shape":{"type":"string","enum":["series","breakdown","scatter"]},"grain":{"nullable":true,"type":"string","enum":["day","hour"]},"unit":{"type":"string"},"goal":{"type":"string","enum":["increase","decrease","neutral"],"description":"Which direction of change is good news."},"aggregate":{"type":"string","enum":["sum","average","latest"],"description":"How values across a range combine into one number."},"description":{"nullable":true,"type":"string"},"axes":{"nullable":true,"type":"object","properties":{"xLabel":{"type":"string"},"xUnit":{"type":"string"},"yLabel":{"type":"string"},"yUnit":{"type":"string"}},"required":["xLabel","xUnit","yLabel","yUnit"],"additionalProperties":false},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hidden":{"type":"boolean"},"headline":{"type":"boolean"},"headlineSlot":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastReportedAt":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"range":{"type":"string","enum":["7d","30d","90d"]},"from":{"type":"string","description":"Inclusive start of the range."},"to":{"type":"string","description":"Exclusive end of the range."},"value":{"nullable":true,"description":"Null when the range holds no points — never 0.","type":"number"},"previousValue":{"nullable":true,"type":"number"},"change":{"nullable":true,"type":"object","properties":{"absolute":{"type":"number"},"ratio":{"type":"number","description":"Change relative to the preceding value's magnitude; -0.4 is a 40% fall."}},"required":["absolute","ratio"],"additionalProperties":false},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"value":{"type":"number"}},"required":["category","value"],"additionalProperties":false}},"points":{"type":"array","items":{"type":"object","properties":{"bucket":{"type":"string","description":"`YYYY-MM-DD` for day grain, an ISO 8601 UTC hour for hour grain."},"category":{"nullable":true,"type":"string"},"value":{"type":"number"}},"required":["bucket","category","value"],"additionalProperties":false}},"scatterPoints":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"x":{"type":"number"},"y":{"type":"number"},"label":{"nullable":true,"type":"string"},"observedAt":{"nullable":true,"type":"string"},"receivedAt":{"type":"string"}},"required":["id","x","y","label","observedAt","receivedAt"],"additionalProperties":false}}},"required":["key","name","displayName","reportedName","shape","grain","unit","goal","aggregate","description","axes","position","hidden","headline","headlineSlot","lastReportedAt","createdAt","updatedAt","range","from","to","value","previousValue","change","categories","points","scatterPoints"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a project metric","description":"Returns one metric's points over a range, with the range value, the preceding range's value, and the change. An empty range has a null value, never 0.","tags":["Projects"],"operationId":"getProjectMetric","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"metricKey","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"}},{"name":"range","in":"query","required":false,"schema":{"default":"30d","description":"The range to summarize, ending today. Defaults to 30 days.","type":"string","enum":["7d","30d","90d"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"patch":{"responses":{"200":{"description":"The updated metric.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string","description":"The name shown: the curated name, else the reported name, else the key."},"displayName":{"nullable":true,"type":"string"},"reportedName":{"nullable":true,"type":"string"},"shape":{"type":"string","enum":["series","breakdown","scatter"]},"grain":{"nullable":true,"type":"string","enum":["day","hour"]},"unit":{"type":"string"},"goal":{"type":"string","enum":["increase","decrease","neutral"],"description":"Which direction of change is good news."},"aggregate":{"type":"string","enum":["sum","average","latest"],"description":"How values across a range combine into one number."},"description":{"nullable":true,"type":"string"},"axes":{"nullable":true,"type":"object","properties":{"xLabel":{"type":"string"},"xUnit":{"type":"string"},"yLabel":{"type":"string"},"yUnit":{"type":"string"}},"required":["xLabel","xUnit","yLabel","yUnit"],"additionalProperties":false},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hidden":{"type":"boolean"},"headline":{"type":"boolean"},"headlineSlot":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastReportedAt":{"nullable":true,"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["key","name","displayName","reportedName","shape","grain","unit","goal","aggregate","description","axes","position","hidden","headline","headlineSlot","lastReportedAt","createdAt","updatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The project already has three headline metrics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The patch is invalid, including headline on a hidden or scatter metric.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Update a project metric's curation","description":"Changes a metric's display name, position, hidden flag, or headline flag. Reports never overwrite these. Hiding a headline metric clears headline; a project has at most three headline metrics, and a scatter metric cannot be headline.","tags":["Projects"],"operationId":"updateProjectMetric","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"metricKey","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"description":"Curated name. Null clears it, so the reported name shows again.","nullable":true,"type":"string","minLength":1,"maxLength":200},"position":{"description":"Sort position among the project's metrics, ascending.","type":"integer","minimum":-1000000,"maximum":1000000},"hidden":{"description":"Hides the metric from readers. Hiding also clears headline.","type":"boolean"},"headline":{"description":"Leads the Metrics tab and the project card. At most 3 per project; a fourth returns 409.","type":"boolean"}}}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"204":{"description":"The metric and its points were deleted.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Delete a project metric","description":"Removes the metric, its curation, and all of its points. A later report naming the same key creates a new metric with default curation.","tags":["Projects"],"operationId":"deleteProjectMetric","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"metricKey","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/changes":{"post":{"responses":{"200":{"description":"The issue tracking this change.","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"changeId":{"type":"string"},"issueId":{"type":"string"},"issueUrl":{"nullable":true,"type":"string"},"identifier":{"nullable":true,"description":"Linear's human key, e.g. ADE-12.","type":"string"},"created":{"type":"boolean","description":"False when an existing issue was updated."},"documentsWritten":{"type":"number"}},"required":["projectId","changeId","issueId","issueUrl","identifier","created","documentsWritten"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"File a change as an issue","description":"Creates or updates the issue tracking one OpenSpec change directory under this project. Khotan owns the issue's title and description; its state, assignee, priority, and dates stay with the tracker. Idempotent by change id.","tags":["Projects"],"operationId":"syncProjectChange","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"changeId":{"type":"string","minLength":1,"maxLength":200,"description":"The change directory name. Stable identity for the issue, so renaming the directory files a second one."},"title":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","maxLength":1000000},"capabilities":{"default":[],"description":"Capabilities this change amends, applied as labels in one managed group. Labels outside that group are untouched.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":80}},"documents":{"default":[],"description":"Long parts of the change — design and requirements — attached as documents rather than inlined in the description.","maxItems":20,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":80,"description":"Stable key for this document, so a re-push updates it rather than adding another."},"title":{"type":"string","minLength":1,"maxLength":200},"content":{"type":"string","maxLength":1000000}},"required":["key","title","content"]}},"source":{"description":"Where the change came from. A url is attached to the issue as a link.","type":"object","properties":{"ref":{"type":"string","minLength":1,"maxLength":200},"url":{"nullable":true,"type":"string","format":"uri"},"label":{"nullable":true,"type":"string","maxLength":200}},"required":["ref"]}},"required":["changeId","title","body"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/projects/{projectId}/linear":{"post":{"responses":{"200":{"description":"The Linear project now bound, or null if cleared.","content":{"application/json":{"schema":{"type":"object","properties":{"bound":{"nullable":true,"type":"string"}},"required":["bound"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Bind a project to Linear","description":"Binds this project to an existing Linear project, or clears the binding when linearProjectId is null. Binding adopts that project's milestones read-only. To create a fresh empty Linear project instead, use the mint action.","tags":["Projects"],"operationId":"bindLinearProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linearProjectId":{"nullable":true,"description":"The Linear project to bind. Null clears the binding.","type":"string","minLength":1}},"required":["linearProjectId"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/linear/initiative":{"post":{"responses":{"200":{"description":"The linked initiative.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"nullable":true,"type":"string"}},"required":["id","name","url"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Link the workspace's Linear initiative","description":"Links this workspace to an existing Linear initiative by id or URL. Minted Linear projects are created beneath it. Restricted to owner and admin.","tags":["Projects"],"operationId":"linkLinearInitiative","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","minLength":1,"maxLength":2048,"description":"The Linear initiative to link, by id or URL. Every Linear project Khotan mints for this workspace is created beneath it."}},"required":["reference"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"get":{"responses":{"200":{"description":"The linked initiative, or null.","content":{"application/json":{"schema":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"nullable":true,"type":"string"}},"required":["id","name","url"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get the workspace's Linear initiative link","description":"Returns the Linear initiative currently linked to this workspace, or null when none is linked.","tags":["Projects"],"operationId":"getLinearInitiative","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"204":{"description":"The initiative link was removed.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Unlink the workspace's Linear initiative","description":"Removes the Linear initiative link from this workspace. Restricted to owner and admin.","tags":["Projects"],"operationId":"unlinkLinearInitiative","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/sqlite-databases":{"get":{"responses":{"200":{"description":"The organization's SQLite databases.","content":{"application/json":{"schema":{"type":"object","properties":{"databases":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"primaryRegion":{"nullable":true,"type":"string"},"regions":{"nullable":true,"type":"array","items":{"type":"string"}},"providerDatabaseName":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","primaryRegion","regions","providerDatabaseName","endpointHost","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"required":["databases"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List SQLite databases","description":"Returns the organization's non-deleted SQLite databases. Responses never include connection credentials.","tags":["SQLite Databases"],"operationId":"listSqliteDatabases","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"post":{"responses":{"201":{"description":"The persisted SQLite database record, ready or failed.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"primaryRegion":{"nullable":true,"type":"string"},"regions":{"nullable":true,"type":"array","items":{"type":"string"}},"providerDatabaseName":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","primaryRegion","regions","providerDatabaseName","endpointHost","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create a SQLite database","description":"Provisions a SQLite database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. This operation is not idempotent: retrying a request that already succeeded provisions a second database.","tags":["SQLite Databases"],"operationId":"createSqliteDatabase","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":63},"idempotencyKey":{"type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/sqlite-databases/{databaseId}":{"get":{"responses":{"200":{"description":"The SQLite database record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["creating","ready","failed"]},"provider":{"type":"string"},"primaryRegion":{"nullable":true,"type":"string"},"regions":{"nullable":true,"type":"array","items":{"type":"string"}},"providerDatabaseName":{"nullable":true,"type":"string"},"endpointHost":{"nullable":true,"type":"string"},"protected":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"readyAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["id","displayName","status","provider","primaryRegion","regions","providerDatabaseName","endpointHost","protected","tags","createdAt","updatedAt","readyAt","failedAt","failureState"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a SQLite database","description":"Returns one SQLite database's record, including status and failure state when present. Responses never include connection credentials.","tags":["SQLite Databases"],"operationId":"getSqliteDatabase","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]},"delete":{"responses":{"200":{"description":"The SQLite database was deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","enum":[true]}},"required":["deleted"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}},"502":{"description":"Response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"summary":"Delete a SQLite database","description":"Tears down the database at the provider, then soft-deletes the record. Deletion is permanent.","tags":["SQLite Databases"],"operationId":"deleteSqliteDatabase","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/sqlite-databases/{databaseId}/tags":{"put":{"responses":{"200":{"description":"The stored tag list, after normalization.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Set a SQLite database's tags","description":"Replaces a SQLite database's tag list with the submitted list and returns the stored list. Tags are normalized to lowercase slugs, so `Live` is stored as `live`. Send an empty list to remove every tag. Requires an API key whose role is owner or developer.","tags":["SQLite Databases"],"operationId":"setSqliteDatabaseTags","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"Full tag list; it replaces the stored list. Each tag is trimmed, lowercased, and has whitespace runs replaced with \"-\", and repeats are removed. After that, each tag is 1-32 lowercase letters, digits, or single hyphens, with at most 10 tags. Presets: test, live, uat, dev, demo, deprecated.","type":"array","items":{"type":"string"}}},"required":["tags"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/organization/brand":{"get":{"responses":{"200":{"description":"The brand, or null.","content":{"application/json":{"schema":{"type":"object","properties":{"brand":{"nullable":true,"type":"object","properties":{"primaryColour":{"nullable":true,"type":"string"},"secondaryColour":{"nullable":true,"type":"string"},"logo":{"nullable":true,"type":"object","properties":{"downloadPath":{"type":"string","description":"Path on the Khotan API host that streams the file."},"publicUrl":{"nullable":true,"description":"Public image URL for browsers, when configured.","type":"string"},"contentType":{"type":"string","enum":["image/png","image/svg+xml"]}},"required":["downloadPath","publicUrl","contentType"],"additionalProperties":false},"appTheme":{"nullable":true,"type":"object","properties":{"light":{"type":"object","additionalProperties":{"type":"string"}},"dark":{"type":"object","additionalProperties":{"type":"string"}}},"required":["light","dark"],"additionalProperties":false},"appThemeSource":{"nullable":true,"type":"string"},"appThemeUpdatedAt":{"nullable":true,"type":"string"},"sourceUrl":{"nullable":true,"type":"string"},"updatedAt":{"type":"string"}},"required":["primaryColour","secondaryColour","logo","appTheme","appThemeSource","appThemeUpdatedAt","sourceUrl","updatedAt"],"additionalProperties":false}},"required":["brand"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get the organization's brand","description":"Returns the stored primary and secondary colours, the app theme, and the logo's download path when one is stored. Returns { brand: null } when nothing is stored.","tags":["Organization"],"operationId":"getOrganizationBrand","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/organization/brand/logo":{"get":{"responses":{"200":{"description":"The logo file.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Download the brand logo","description":"Returns the logo file (PNG or SVG). Responds 404 when no logo is stored.","tags":["Organization"],"operationId":"getOrganizationBrandLogo","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/organization/brand/app-theme":{"put":{"responses":{"200":{"description":"The saved app theme.","content":{"application/json":{"schema":{"type":"object","properties":{"appTheme":{"type":"object","properties":{"light":{"type":"object","additionalProperties":{"type":"string"}},"dark":{"type":"object","additionalProperties":{"type":"string"}}},"required":["light","dark"],"additionalProperties":false},"appThemeSource":{"nullable":true,"type":"string"},"appThemeUpdatedAt":{"type":"string"}},"required":["appTheme","appThemeSource","appThemeUpdatedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Save the brand's app theme","description":"Replaces the organization's app theme with the given light and dark CSS colour tokens. Token names must match ^--[a-z0-9-]+$ and values must be hex or oklch() colours. Logos and colours are unchanged.","tags":["Organization"],"operationId":"setOrganizationBrandAppTheme","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"appTheme":{"type":"object","properties":{"light":{"type":"object","additionalProperties":{"type":"string"}},"dark":{"type":"object","additionalProperties":{"type":"string"}}},"required":["light","dark"]},"source":{"nullable":true,"type":"string","minLength":1,"maxLength":200}},"required":["appTheme"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox":{"get":{"responses":{"200":{"description":"The workspace inbox.","content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string"},"displayName":{"type":"string"}},"required":["address","displayName"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get the workspace inbox","description":"Returns the address and sender name of the workspace's email inbox on agents.khotan.com, creating the inbox if the workspace has none yet.","tags":["Inbox"],"operationId":"getWorkspaceInbox","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox/threads":{"get":{"responses":{"200":{"description":"Email threads.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"List email threads","description":"Lists the workspace inbox's email threads, newest first. Filter by text in a sender or the subject. Set excludeInternal=true to leave out email that staff sent from the internal channel.","tags":["Inbox"],"operationId":"listInboxThreads","parameters":[{"name":"limit","in":"query","required":false,"schema":{"default":10,"type":"integer","minimum":1,"maximum":25}},{"name":"pageToken","in":"query","required":false,"schema":{"type":"string","minLength":1}},{"name":"sender","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"subject","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"excludeInternal","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox/threads/{threadId}":{"get":{"responses":{"200":{"description":"The email thread.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Read an email thread","description":"Returns every message in one email thread of the workspace inbox, with each message's sender, recipients, body text, and attachments.","tags":["Inbox"],"operationId":"getInboxThread","parameters":[{"name":"threadId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"excludeInternal","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox/messages/{messageId}/attachments/{attachmentId}":{"get":{"responses":{"200":{"description":"The attachment links.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get an email attachment","description":"Returns an attachment's name, type, and size, with short-lived links to its bytes and, when available, its extracted text.","tags":["Inbox"],"operationId":"getInboxAttachment","parameters":[{"name":"messageId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"excludeInternal","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox/drafts":{"post":{"responses":{"201":{"description":"The draft.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Create an email draft","description":"Creates an unsent draft in the workspace inbox: a new email with recipients and a subject, or a reply to a message in the inbox. Nothing is sent until the draft is sent.","tags":["Inbox"],"operationId":"createInboxDraft","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"cc":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"subject":{"type":"string","maxLength":300},"text":{"type":"string","minLength":1,"maxLength":50000},"replyToMessageId":{"type":"string","minLength":1},"replyAll":{"default":false,"type":"boolean"},"internal":{"default":false,"type":"boolean"},"slackThread":{"type":"object","properties":{"teamId":{"type":"string","pattern":"^[A-Z0-9]+$"},"channelId":{"type":"string","pattern":"^[A-Z0-9]+$"},"threadTs":{"type":"string","pattern":"^\\d+\\.\\d+$"}},"required":["teamId","channelId","threadTs"]}},"required":["text"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox/drafts/{draftId}":{"delete":{"responses":{"204":{"description":"The draft was discarded.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Discard an email draft","description":"Deletes an unsent draft from the workspace inbox, so it can never be sent.","tags":["Inbox"],"operationId":"discardInboxDraft","parameters":[{"name":"draftId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/inbox/drafts/{draftId}/send":{"post":{"responses":{"200":{"description":"The sent email.","headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Send an email draft","description":"Sends a draft from the workspace inbox. A draft sends once; after it sends, it no longer exists.","tags":["Inbox"],"operationId":"sendInboxDraft","parameters":[{"name":"draftId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/feedback":{"post":{"responses":{"201":{"description":"The stored submission's id and time, and a note to carry on.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"createdAt":{"type":"string","description":"ISO 8601 timestamp."},"message":{"type":"string"}},"required":["id","createdAt","message"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"400":{"description":"The request was malformed or failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Send feedback to the Khotan team","description":"Stores a feature request, product direction, docs gap, or bug from an agent. The agent states its proposal, the situation it was in, its reason, and whether a person asked it to send it. The server attributes the submission from the API key; for the Slack agent it also records the conversation and the member who asked. Every authenticated caller can send feedback, including the code sandbox's read-only key. Nothing reads submissions back over the API.","tags":["Feedback"],"operationId":"submitAgentFeedback","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["feature_request","direction","docs_gap","bug","other"],"description":"What kind of feedback this is."},"title":{"type":"string","minLength":1,"maxLength":200,"description":"One line."},"proposal":{"type":"string","minLength":1,"maxLength":4000,"description":"What should be added or changed."},"situation":{"type":"string","minLength":1,"maxLength":4000,"description":"What the agent was doing: the task, its goal, and what it tried."},"reason":{"type":"string","minLength":1,"maxLength":4000,"description":"Why the agent sent it: what it had to do instead, and what the change would let it or its user do."},"origin":{"type":"string","enum":["agent","user"],"description":"user when a person asked the agent to send it; agent when the agent decided to."},"workaround":{"type":"string","maxLength":4000},"impact":{"type":"string","enum":["blocked","workaround","nice_to_have"]},"area":{"type":"string","enum":["api","cli","mcp","slack_agent","dashboard","templates","skills","docs","other"]},"capabilityIds":{"description":"Khotan action ids this is about. A requested action may not exist yet.","maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":200}},"threadId":{"description":"The caller's own conversation or session id.","type":"string","maxLength":200},"model":{"type":"string","maxLength":200},"client":{"description":"The agent product, such as cursor or claude-code.","type":"string","maxLength":200}},"required":["kind","title","proposal","situation","reason","origin"]}}}},"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/branches/{branchId}/schema-promotions":{"post":{"responses":{"201":{"description":"The persisted promotion plan. Status is ready only when SQL validation passes and no blocked changes exist.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"databaseId":{"type":"string"},"branchId":{"type":"string"},"status":{"type":"string","enum":["planning","ready","blocked","applying","applied","failed"]},"sourceSchemaHash":{"type":"string"},"targetSchemaHash":{"type":"string"},"summary":{"type":"object","properties":{"statementCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"createdSchemas":{"type":"array","items":{"type":"string"}},"createdTypes":{"default":[],"type":"array","items":{"type":"string"}},"createdTables":{"type":"array","items":{"type":"string"}},"addedColumns":{"type":"array","items":{"type":"string"}},"createdIndexes":{"type":"array","items":{"type":"string"}},"createdConstraints":{"type":"array","items":{"type":"string"}},"blockedCount":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["statementCount","createdSchemas","createdTypes","createdTables","addedColumns","createdIndexes","createdConstraints","blockedCount"],"additionalProperties":false},"blockedChanges":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"objectType":{"type":"string","enum":["schema","table","column","constraint","index","type"]},"objectName":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message","objectType","objectName"],"additionalProperties":false}},"sql":{"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["blocked_changes","schema_unavailable","validation_failed","schema_changed","apply_failed","provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"failedStatementIndex":{"nullable":true,"type":"integer","minimum":0,"maximum":9007199254740991},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"validatedAt":{"nullable":true,"type":"string"},"appliedAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"}},"required":["id","databaseId","branchId","status","sourceSchemaHash","targetSchemaHash","summary","blockedChanges","sql","failureState","failedStatementIndex","createdAt","updatedAt","validatedAt","appliedAt","failedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The caller is not permitted to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Plan a database schema promotion","description":"Compares a ready branch schema against the production database schema, records a schema-only promotion plan, validates generated SQL on a temporary provider branch, and returns the persisted plan. It never copies branch data to production.","tags":["Databases"],"operationId":"planDatabaseSchemaPromotion","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"branchId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/schema-promotions/{promotionId}":{"get":{"responses":{"200":{"description":"The schema promotion record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"databaseId":{"type":"string"},"branchId":{"type":"string"},"status":{"type":"string","enum":["planning","ready","blocked","applying","applied","failed"]},"sourceSchemaHash":{"type":"string"},"targetSchemaHash":{"type":"string"},"summary":{"type":"object","properties":{"statementCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"createdSchemas":{"type":"array","items":{"type":"string"}},"createdTypes":{"default":[],"type":"array","items":{"type":"string"}},"createdTables":{"type":"array","items":{"type":"string"}},"addedColumns":{"type":"array","items":{"type":"string"}},"createdIndexes":{"type":"array","items":{"type":"string"}},"createdConstraints":{"type":"array","items":{"type":"string"}},"blockedCount":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["statementCount","createdSchemas","createdTypes","createdTables","addedColumns","createdIndexes","createdConstraints","blockedCount"],"additionalProperties":false},"blockedChanges":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"objectType":{"type":"string","enum":["schema","table","column","constraint","index","type"]},"objectName":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message","objectType","objectName"],"additionalProperties":false}},"sql":{"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["blocked_changes","schema_unavailable","validation_failed","schema_changed","apply_failed","provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"failedStatementIndex":{"nullable":true,"type":"integer","minimum":0,"maximum":9007199254740991},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"validatedAt":{"nullable":true,"type":"string"},"appliedAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"}},"required":["id","databaseId","branchId","status","sourceSchemaHash","targetSchemaHash","summary","blockedChanges","sql","failureState","failedStatementIndex","createdAt","updatedAt","validatedAt","appliedAt","failedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Get a database schema promotion","description":"Returns the recorded schema promotion plan, including SQL, summary, blocked changes, and failure state.","tags":["Databases"],"operationId":"getDatabaseSchemaPromotion","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"promotionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}},"/api/v1/databases/{databaseId}/schema-promotions/{promotionId}/apply":{"post":{"responses":{"200":{"description":"The applied promotion record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"databaseId":{"type":"string"},"branchId":{"type":"string"},"status":{"type":"string","enum":["planning","ready","blocked","applying","applied","failed"]},"sourceSchemaHash":{"type":"string"},"targetSchemaHash":{"type":"string"},"summary":{"type":"object","properties":{"statementCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"createdSchemas":{"type":"array","items":{"type":"string"}},"createdTypes":{"default":[],"type":"array","items":{"type":"string"}},"createdTables":{"type":"array","items":{"type":"string"}},"addedColumns":{"type":"array","items":{"type":"string"}},"createdIndexes":{"type":"array","items":{"type":"string"}},"createdConstraints":{"type":"array","items":{"type":"string"}},"blockedCount":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["statementCount","createdSchemas","createdTypes","createdTables","addedColumns","createdIndexes","createdConstraints","blockedCount"],"additionalProperties":false},"blockedChanges":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"objectType":{"type":"string","enum":["schema","table","column","constraint","index","type"]},"objectName":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message","objectType","objectName"],"additionalProperties":false}},"sql":{"type":"string"},"failureState":{"nullable":true,"type":"object","properties":{"code":{"type":"string","enum":["blocked_changes","schema_unavailable","validation_failed","schema_changed","apply_failed","provider_unavailable","provider_rejected","internal_error"]},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false},"failedStatementIndex":{"nullable":true,"type":"integer","minimum":0,"maximum":9007199254740991},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"validatedAt":{"nullable":true,"type":"string"},"appliedAt":{"nullable":true,"type":"string"},"failedAt":{"nullable":true,"type":"string"}},"required":["id","databaseId","branchId","status","sourceSchemaHash","targetSchemaHash","summary","blockedChanges","sql","failureState","failedStatementIndex","createdAt","updatedAt","validatedAt","appliedAt","failedAt"],"additionalProperties":false}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}}}},"401":{"description":"Authentication is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The promotion is not ready, the schemas changed after planning, or SQL application failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; retry after the indicated delay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"RateLimit-Limit":{"description":"Configured request quota for this route's rate-limit window (decimal integer).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window after this response (decimal integer, never negative).","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Whole seconds until the caller may expect capacity to free in the sliding window.","schema":{"type":"integer"}},"Retry-After":{"description":"Whole seconds until the caller may retry after a rate-limit rejection.","schema":{"type":"integer"}}}}},"summary":"Apply a database schema promotion","description":"Applies the exact reviewed schema-only promotion SQL to the production database. The stored source and target schema hashes must still match, otherwise the request fails with 409.","tags":["Databases"],"operationId":"applyDatabaseSchemaPromotion","parameters":[{"name":"databaseId","in":"path","required":true,"schema":{"type":"string","minLength":1}},{"name":"promotionId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}]}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organization-scoped API key sent as `Authorization: Bearer <key>`."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key","description":"Organization-scoped API key sent as the `x-api-key` header."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}}}}}}}}