> For the complete documentation index, see [llms.txt](https://docs.khotan.com/llms.txt). A markdown version of any page is available at its `index.md` URL.

# Apps

`/api/v1` operations for apps. See [HTTP API](/api/) for authentication, the error envelope, and rate limits.

## Operations

### GET /api/v1/apps

List apps

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.

Auth: API key required.

Operation ID: `listApps`

Responses:
- `200` — The organization's apps.
- `401` — Authentication is missing or invalid.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps list`

### POST /api/v1/apps

Create an app

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.

Auth: API key required.

Operation ID: `createApp`

Request body (`application/json`, required):
- `name` (string, required)
- `regionPreference` (string | null, optional)
- `domain` (string | null, optional) — Khotan-managed domain namespace for the app's host, such as khotan.app or apps.khotan.com. Omit to use the default namespace.
- `environmentVariables` (object[], optional)
- `database` (object, optional)
- `redis` (object, optional)
- `idempotencyKey` (string, optional)

Responses:
- `201` — The persisted app record, in ready or failed status.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `403` — The caller is not permitted to perform this action.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps create`

### POST /api/v1/apps/external

Link an external app

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.

Auth: API key required.

Operation ID: `linkExternalApp`

Request body (`application/json`, required):
- `name` (string, required)
- `vercelProjectId` (string, required)
- `productionUrl` (string, required)
- `githubRepositoryUrl` (string, required)
- `defaultBranch` (string, required)

Responses:
- `200` — The existing external app matched by its references.
- `201` — The newly linked external app.
- `401` — Authentication is missing or invalid.
- `403` — The caller is not permitted to perform this action.
- `404` — The resource was not found.
- `422` — Response.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

### GET /api/v1/apps/{appId}

Get an app

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.

Auth: API key required.

Operation ID: `getApp`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The app record.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps get`

### DELETE /api/v1/apps/{appId}

Delete an app

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.

Auth: API key required.

Operation ID: `deleteApp`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The app was deleted.
- `401` — Authentication is missing or invalid.
- `403` — The app is protected from deletion (code resource_protected). Remove protection from the dashboard before deleting via the API.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps delete`

### PUT /api/v1/apps/{appId}/tags

Set an app's tags

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.

Auth: API key required.

Operation ID: `setAppTags`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `tags` (string[], required) — 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.

Responses:
- `200` — The stored tag list, after normalization.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `403` — The caller is not permitted to perform this action.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps tags set`

### POST /api/v1/apps/{appId}/redeploy

Redeploy an app

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.

Auth: API key required.

Operation ID: `redeployApp`

Path parameters:
- `appId` (string, required)

Responses:
- `202` — The new deployment's initial status.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps redeploy`

### GET /api/v1/apps/{appId}/setup

Get app setup

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.

Auth: API key required.

Operation ID: `getAppSetup`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The newest run, or null.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps setup status`

### POST /api/v1/apps/{appId}/setup

Start app setup

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.

Auth: API key required.

Operation ID: `startAppSetup`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `name` (string, required)
- `website` (string, optional)
- `logo` (string | null, optional)
- `idempotencyKey` (string, optional) — Repeat a start with the same key to get the same run back instead of starting a second one.

Responses:
- `202` — The new run, in status running.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `403` — The caller is not permitted to perform this action.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.
- `503` — Response.

CLI: `khotan apps setup`

### POST /api/v1/apps/{appId}/setup/pull-request

Open or merge the app's setup PR

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.

Auth: API key required.

Operation ID: `upsertAppSetupPullRequest`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `title` (string, required)
- `body` (string, required)
- `merge` (boolean, optional) — Merge the PR when every guard holds. Only the running setup run's own key can merge.

Responses:
- `200` — The setup PR, and whether it merged.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `403` — The caller is not permitted to perform this action.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

### GET /api/v1/apps/{appId}/deployments

List an app's deployments

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.

Auth: API key required.

Operation ID: `listAppDeployments`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The app's recent deployments.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps deployments`

### GET /api/v1/apps/{appId}/source

Download an app's source

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.

Auth: API key required.

Operation ID: `getAppSource`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The app's source files.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — The app exists but its source repository is not ready yet.
- `413` — The source is too large to download as a single response.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps source`

### GET /api/v1/apps/{appId}/env

List environment variables

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.

Auth: API key required.

Operation ID: `listAppEnvVars`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — Environment variable metadata, never values.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps env list`

### POST /api/v1/apps/{appId}/env

Bulk upsert environment variables

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.

Auth: API key required.

Operation ID: `bulkUpsertAppEnvVars`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `variables` (object[], required)
- `target` (array<"production" | "preview" | "development">, optional)

Responses:
- `200` — The partial result: which keys were upserted and which failed.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps env bulk-set`

### GET /api/v1/apps/{appId}/env/{key}

Reveal an environment variable

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.

Auth: API key required.

Operation ID: `revealAppEnvVar`

Path parameters:
- `appId` (string, required)
- `key` (string, required)

Query parameters:
- `target` ("production" | "preview" | "development", optional)

Responses:
- `200` — The decrypted value.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps env reveal`

### PUT /api/v1/apps/{appId}/env/{key}

Upsert an environment variable

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.

Auth: API key required.

Operation ID: `upsertAppEnvVar`

Path parameters:
- `appId` (string, required)
- `key` (string, required)

Request body (`application/json`, required):
- `value` (string, required)
- `target` (array<"production" | "preview" | "development">, optional)

Responses:
- `200` — The variable was written; a redeploy is required.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps env set`

### DELETE /api/v1/apps/{appId}/env/{key}

Delete an environment variable

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.

Auth: API key required.

Operation ID: `deleteAppEnvVar`

Path parameters:
- `appId` (string, required)
- `key` (string, required)

Query parameters:
- `target` ("production" | "preview" | "development", optional)

Responses:
- `200` — The variable was removed (requiresRedeploy=true) or did not exist (requiresRedeploy=false).
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps env unset`

### GET /api/v1/apps/{appId}/email-subscriptions

List a deployment's email subscriptions

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.

Auth: API key required.

Operation ID: `listAppEmailSubscriptions`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The deployment's email subscriptions.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps email-subscriptions list`

### POST /api/v1/apps/{appId}/email-subscriptions

Subscribe a deployment to a mailbox

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.

Auth: API key required.

Operation ID: `createAppEmailSubscription`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `emailIntegrationId` (string, required)
- `deliveryUrl` (string, required)

Responses:
- `201` — The created subscription. For Outlook it includes the clientState secret.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps email-subscriptions create`

### GET /api/v1/apps/{appId}/email-subscriptions/{subscriptionId}

Get a deployment's email subscription

Auth: API key required.

Operation ID: `getAppEmailSubscription`

Path parameters:
- `appId` (string, required)
- `subscriptionId` (string, required)

Responses:
- `200` — The subscription.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps email-subscriptions get`

### DELETE /api/v1/apps/{appId}/email-subscriptions/{subscriptionId}

Unsubscribe a deployment from a mailbox

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.

Auth: API key required.

Operation ID: `deleteAppEmailSubscription`

Path parameters:
- `appId` (string, required)
- `subscriptionId` (string, required)

Responses:
- `200` — The subscription was removed.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.
- `502` — Response.

CLI: `khotan apps email-subscriptions delete`

### GET /api/v1/apps/{appId}/email-subscriptions/{subscriptionId}/client-state

Get a deployment's Outlook subscription client state (secret)

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.

Auth: API key required.

Operation ID: `getAppEmailSubscriptionClientState`

Path parameters:
- `appId` (string, required)
- `subscriptionId` (string, required)

Responses:
- `200` — The subscription's client state. Secret-bearing.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

### GET /api/v1/apps/{appId}/oauth

Get app OAuth status

Returns non-secret OAuth client status, effective redirect URIs, and setup availability.

Auth: API key required.

Operation ID: `getAppOAuthStatus`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The OAuth client status.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth status`

### DELETE /api/v1/apps/{appId}/oauth

Disconnect app OAuth

Revokes the OAuth client and removes its injected environment variables.

Auth: API key required.

Operation ID: `disconnectAppOAuth`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The OAuth client was disconnected.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth disconnect`

### POST /api/v1/apps/{appId}/oauth/setup

Set up app OAuth

Creates the OAuth client once and returns its plaintext secret. Returns a conflict when credentials already exist.

Auth: API key required.

Operation ID: `setupAppOAuth`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The new OAuth credentials.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth setup`

### POST /api/v1/apps/{appId}/oauth/sync

Sync app OAuth

Repairs redirect and environment configuration without rotating the client secret.

Auth: API key required.

Operation ID: `syncAppOAuth`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The synchronized OAuth configuration.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth sync`

### POST /api/v1/apps/{appId}/oauth/rotate

Rotate app OAuth secret

Invalidates the current secret, injects a new one, and optionally starts a fresh deployment.

Auth: API key required.

Operation ID: `rotateAppOAuth`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `redeploy` (boolean, optional)

Responses:
- `200` — The rotated credentials and redeploy result.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth rotate`

### PUT /api/v1/apps/{appId}/oauth/redirect-uris

Replace app OAuth redirect URIs

Replaces operator-managed redirect URIs while preserving the managed production callback.

Auth: API key required.

Operation ID: `setAppOAuthRedirectUris`

Path parameters:
- `appId` (string, required)

Request body (`application/json`, required):
- `redirectUris` (string[], optional)

Responses:
- `200` — The effective redirect URI set.
- `400` — The request was malformed or failed validation.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth set-redirects`

### POST /api/v1/apps/{appId}/oauth/export

Export app OAuth environment

Returns secret-bearing .env.local content for the existing OAuth client.

Auth: API key required.

Operation ID: `exportAppOAuth`

Path parameters:
- `appId` (string, required)

Responses:
- `200` — The .env.local content.
- `401` — Authentication is missing or invalid.
- `404` — The resource was not found.
- `409` — Response.
- `429` — Too many requests; retry after the indicated delay.

CLI: `khotan apps oauth export`

<!-- generated from the /api/v1 OpenAPI spec v1.0.0 (adapter 0.10.0); do not edit by hand -->
