diff --git a/docs/README.md b/docs/README.md
index 89f179e2..4c1d5c97 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -133,3 +133,5 @@ See [Authentication](./authentication.md) for account, session, email, and OAuth
See [Functions](./functions.md) for standard invocation, durable execution, local durable development, and error handling.
See [Versions and compatibility](./versions.md) for runtime support, upgrades, and restoring a tested dependency set.
+
+- [Sandboxes](sandboxes.md): isolated commands, sessions, files, and HTTP access.
diff --git a/docs/sandboxes.md b/docs/sandboxes.md
new file mode 100644
index 00000000..c62f60f8
--- /dev/null
+++ b/docs/sandboxes.md
@@ -0,0 +1,98 @@
+---
+title: Sandboxes
+description: Run isolated commands and manage sessions, files, and HTTP services from Python.
+---
+
+Run a command from trusted backend code in an environment with Sandbox access enabled:
+
+```python
+import os
+from uuid import uuid4
+from volcano_sdk import VolcanoClient
+
+client = VolcanoClient(
+ anon_key=os.environ["VOLCANO_ANON_KEY"],
+ service_key=os.environ["VOLCANO_SERVICE_KEY"],
+ api_url=os.environ.get("VOLCANO_API_URL", "https://api.volcano.dev"),
+)
+project_id = os.environ["VOLCANO_PROJECT_ID"]
+request_id = str(uuid4())
+result = client.sandboxes.exec(
+ project_id,
+ 'python -c "print(42)"',
+ region="aws-us-east-1",
+ preset="python3.12",
+ request_id=request_id,
+)
+print(result.stdout, result.exit_code)
+```
+
+Keep the same `request_id` when retrying an uncertain create or execution. A new
+ID represents a new operation. The SDK does not replay commands after transport failures. A rejected user token
+is refreshed once; the retry preserves the request ID.
+Nonzero command exits and timeouts are result fields, not API exceptions.
+API failures raise typed exceptions such as `ConflictError` or `RateLimitedError`;
+these preserve `status`, `code`, and `retry_after` when supplied by the server.
+
+## Keep a session
+
+```python
+import time
+
+with client.sandboxes.create(
+ project_id,
+ region="aws-us-east-1",
+ preset="python3.12",
+ max_duration_seconds=300,
+) as session:
+ for _ in range(60):
+ if session.refresh().state == "running":
+ break
+ time.sleep(1)
+ else:
+ raise TimeoutError("Sandbox did not become ready")
+ session.files.write("/tmp/input.bin", bytes(range(256)))
+ assert session.files.read("/tmp/input.bin") == bytes(range(256))
+ result = session.exec("wc -c /tmp/input.bin")
+ print(result.stdout)
+```
+
+Creation, suspension, resumption, and termination are asynchronous. Use `refresh()`
+to observe state. Context exit requests termination even when the body raises;
+it does not wait for termination to complete. Use `get(session_id)` to reconnect,
+then `suspend()`, `resume()`, or `terminate()` as needed. File reads return `bytes`;
+writes accept at most 8 MiB.
+
+## Access a background HTTP service
+
+Inside a running session, detach the service and redirect its streams:
+
+```python
+session.exec(
+ "nohup python -m http.server 8080 --bind 0.0.0.0 >/tmp/http.log 2>&1 ` tag; project IDs are unguessable UUIDs and logos are
- non-sensitive branding. The `Project.logo_url` field exposes a versioned
- path to this endpoint.
- operationId: getProjectLogo
- security: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- responses:
- '200':
- description: Logo image
- content:
- image/png:
- schema:
- type: string
- format: binary
- image/jpeg:
- schema:
- type: string
- format: binary
- image/gif:
- schema:
- type: string
- format: binary
- image/webp:
- schema:
- type: string
- format: binary
- image/svg+xml:
- schema:
- type: string
- format: binary
- '404':
- description: Project or logo not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- post:
- tags:
- - Projects
- summary: Upload or replace the project logo
+ - Git Connections
+ summary: List repos accessible to a connection through an installation
description: |
- Uploads an image as the project's logo, storing it in the project's
- storage folder. Accepts PNG, JPEG, GIF, WebP, or SVG up to 2 MB. Replaces any
- existing logo. Returns the updated project, whose `logo_url` reflects
- the new logo.
- operationId: uploadProjectLogo
+ Live proxy to GitHub: lists the repos the connection's stored user
+ token can access through installationId. Nothing is persisted by this
+ call.
+ operationId: listGitInstallationRepositories
security:
- UserToken: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- required:
- - logo
- properties:
- logo:
- type: string
- format: binary
- description: Logo image (PNG, JPEG, GIF, WebP, or SVG; max 2 MB)
+ - name: connectionId
+ in: path
+ required: true
+ description: Connection ID to browse repositories for.
+ schema:
+ type: string
+ format: uuid
+ - name: installationId
+ in: path
+ required: true
+ description: GitHub App installation ID.
+ schema:
+ type: integer
+ format: int64
responses:
'200':
- description: Logo uploaded
+ description: Repositories accessible through the installation
content:
application/json:
schema:
- $ref: '#/components/schemas/Project'
+ $ref: '#/components/schemas/GitRepositoriesResponse'
'400':
- description: Bad request (missing file, unsupported type, or too large)
+ description: Malformed connection or installation ID
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project not found
+ description: Connection not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: Project is being deleted
+ '500':
+ description: Failed to list repositories
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Logo storage is not configured
+ description: Git provider integration is not configured
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ /github/callback:
+ get:
tags:
- - Projects
- summary: Remove the project logo
+ - Git Connections
+ summary: Complete a GitHub App connection callback
description: |
- Deletes the project logo from the project's storage folder and clears its
- record. Returns the updated project with no `logo_url`.
- operationId: deleteProjectLogo
- security:
- - UserToken: []
+ Public GitHub App callback. The signed state and callback binding cookie
+ bind the provider authorization to the browser that started the flow.
+ operationId: gitConnectCallback
+ security: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - name: code
+ in: query
+ required: false
+ description: GitHub user authorization code.
+ schema:
+ type: string
+ - name: state
+ in: query
+ required: true
+ description: Signed connect state generated by startGitConnect.
+ schema:
+ type: string
+ - name: error
+ in: query
+ required: false
+ description: Provider error returned by GitHub.
+ schema:
+ type: string
responses:
- '200':
- description: Logo removed
- content:
- application/json:
+ '303':
+ description: Redirect back to the app after a successful or failed connect attempt
+ headers:
+ Location:
+ description: Redirect target
schema:
- $ref: '#/components/schemas/Project'
- '403':
- description: Access denied
+ type: string
+ '400':
+ description: Invalid callback request or state
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ '429':
+ description: Too many callback attempts from this client
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: Project is being deleted
+ '500':
+ description: Failed to complete the connection
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Logo storage is not configured
+ description: Git provider integration is not configured
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/usage:
+ /projects:
get:
tags:
- Projects
- summary: Get usage metrics for a project
+ summary: List all projects for authenticated user
description: |
- Returns project usage totals for the current usage month plus
- recent hourly and daily time series for each tracked metric.
- operationId: getProjectUsage
+ Returns projects that are not deleting or deleted, newest first.
+ Supports two mutually exclusive pagination modes. Offset mode uses
+ `page` and `limit`. Cursor mode uses `cursor` or `ending_before` with
+ `limit`, returns `next_cursor`/`prev_cursor`, and supports a bounded
+ `offset` past the cursor anchor. Supplying `limit` without `page`
+ selects cursor mode. `search` applies a case-insensitive project-name
+ filter in either mode. `include` optionally expands each returned
+ project with its Git connection and/or aggregate health summary using
+ `git_connection` and `health`. Sending `page` with `cursor` or `ending_before`,
+ or sending both cursor directions, returns 400.
+ operationId: listProjects
security:
- UserToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
+ - name: include
+ in: query
+ required: false
+ description: Optional comma-separated project metadata expansions.
+ style: form
+ explode: false
+ schema:
+ type: array
+ uniqueItems: true
+ items:
+ type: string
+ enum:
+ - git_connection
+ - health
responses:
'200':
- description: Usage metrics retrieved
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectUsageResponse'
- '403':
- description: Access denied
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ $ref: '#/components/schemas/PaginatedProjects'
+ '400':
+ description: Invalid or conflicting pagination parameters
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/shared-variables:
- put:
+ post:
tags:
- Projects
- summary: Replace shared variable names
+ summary: Create a new project
description: |
- Atomically replaces the complete shared function-variable list without
- changing values. Names must already exist. Validates final affected
- function environments before membership or propagation side effects.
- An empty list clears membership. Omitted names remain stored as non-shared variables.
- operationId: replaceSharedVariables
+ Creates a project for the authenticated user.
+ Each user can create up to 1,000 projects. Requests over this cap return 403.
+ operationId: createProject
security:
- UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- additionalProperties: false
- required:
- - shared_variables
- not:
- required:
- - expected_shared_variables
- - expected_shared_variables_digest
- properties:
- shared_variables:
- type: array
- uniqueItems: true
- items:
- type: string
- maxLength: 256
- pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
- expected_shared_variables:
- type: array
- uniqueItems: true
- description: When present, replace only if the current complete shared list matches this list.
- items:
- type: string
- maxLength: 256
- pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
- expected_shared_variables_digest:
- type: string
- minLength: 64
- maxLength: 64
- pattern: ^[a-f0-9]{64}$
- description: SHA-256 of the sorted unique current shared names joined by a newline. Use instead of expected_shared_variables for a compact conditional replacement.
+ $ref: '#/components/schemas/CreateProjectRequest'
responses:
- '204':
- description: Shared list replaced and affected function synchronization started.
- '400':
- description: Invalid names or final function environment.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized
- '404':
- description: Project not found
- '409':
- description: Shared list changed since it was read.
+ '201':
+ description: Project created
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '413':
- description: Request body exceeds 4,194,304 bytes
+ $ref: '#/components/schemas/Project'
+ '400':
+ description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Persistence or synchronization failed
- '503':
- description: Shared variable membership writes are disabled during rollout
+ '403':
+ description: Project limit exceeded for the user
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/config:
+ /projects/{id}:
get:
tags:
- Projects
- summary: Export project configuration
- description: |
- Exports the project's current user-facing configuration as a
- declarative manifest. Returns JSON by default. Request the canonical
- volcano-config.yaml rendering with `Accept: application/yaml` or
- `?format=yaml`; the YAML is returned verbatim as the raw response body
- (`Content-Type: application/yaml`) and is meant to be saved as-is.
- Variable values and write-only secrets (SMTP password, OAuth client secrets, TLS material)
- are omitted from the export; shared_variables contains names only; the YAML rendering adds a header comment
- describing how to set them via CLI environment interpolation.
- operationId: getProjectConfig
+ summary: Get project by ID
+ operationId: getProject
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: format
- in: query
- required: false
- description: Response format override. Takes precedence over the Accept header.
- schema:
- type: string
- enum:
- - json
- - yaml
responses:
'200':
- description: |
- Current project configuration. JSON by default; when YAML is
- requested the body is the canonical volcano-config.yaml document
- served verbatim with `Content-Type: application/yaml`.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectConfig'
- application/yaml:
- schema:
- type: string
- format: binary
- description: |
- Canonical volcano-config.yaml document returned verbatim,
- ready to be saved as-is. The response body is limited to
- 4,194,304 bytes, matching the configuration apply limit.
- '401':
- description: Unauthorized - invalid or missing token
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/Project'
'404':
description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '413':
- description: Canonical YAML export exceeds 4,194,304 bytes
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- put:
+ patch:
tags:
- Projects
- summary: Apply project configuration
- description: |
- Validates and applies a declarative configuration manifest to the
- project, reconciling each declared section and returning a per-resource
- report. Omitted sections are untouched. Declared collection keys
- (`variables`, `buckets[].policies`, `auth.providers.oauth`,
- `auth.email.templates`, `functions[].schedulers`) are fully synced:
- resources absent from the manifest are deleted. Functions, frontends,
- databases, and buckets are never created or deleted; manifest entries
- for resources that do not exist are skipped and reported in `skipped`,
- and existing resources missing from a declared section are reported in
- `missing`. Validation failures (including plan-gate violations) return
- 422 and nothing is applied. Set `dry_run=true` to get the projected
- report without applying changes. Applies are serialized per project;
- a concurrent apply returns 409.
- operationId: applyProjectConfig
+ summary: Update project metadata and region policy
+ operationId: updateProject
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: dry_run
- in: query
- required: false
- description: Validate and report projected actions without applying changes.
- schema:
- type: boolean
- default: false
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectConfig'
+ $ref: '#/components/schemas/UpdateProjectRequest'
responses:
'200':
- description: |
- Apply report. Individual entries may still carry `action: error`
- for apply-phase failures (summary.errors > 0); already-applied
- changes are not rolled back.
+ description: Project updated
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectConfigApplyResult'
+ $ref: '#/components/schemas/Project'
'400':
- description: Malformed request body
+ description: |
+ Bad request (no region selected, an unknown region, or — for a
+ project holding durable functions — a region that does not offer
+ durable execution)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ '403':
+ description: Forbidden (for example, selecting subset regions on non-PRO plan)
content:
application/json:
schema:
@@ -1633,58 +1780,58 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: Another apply is in progress for this project
+ description: Conflict (project name already exists or a resource deployment blocks a region change)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '422':
- description: Manifest validation failed; nothing was applied
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectConfigValidationErrorResponse'
- '503':
- description: Shared variable membership writes are disabled during rollout
+ delete:
+ tags:
+ - Projects
+ summary: Delete a project
+ description: |
+ Starts asynchronous project deletion. The project remains available from
+ `GET /projects/{id}` with `status: deleting` until cleanup finishes, but is
+ removed from project lists as soon as deletion starts. After cleanup it
+ returns 404.
+ operationId: deleteProject
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '202':
+ description: Project deletion started
+ '404':
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/source-export:
+ /projects/{id}/health:
get:
tags:
- Projects
- - Git Connections
- summary: Report the project's source-of-truth state
- operationId: getProjectSourceExport
+ summary: Get project health
description: |
- Volcano stores the source of the functions and frontend it runs for a
- project. This reports whether that source has been written to the
- connected repository, and whether the repository has taken over as the
- project's source of truth.
-
- `mode` is `platform`, `git_exporting`, `git_pending`, or `git`. Export
- enters `git_exporting` before reading stored source. GitHub's signed
- push event confirms that the initial commit reached the production
- branch. That push or a newer production push changes the mode to
- `git_pending` when it starts a deployment. `exported_at` records that
- transition.
-
- A successful Git run completes the transition when it matches the
- recorded repository, production branch, and root directory and actually
- dispatches every recorded resource. Ordinary production-branch pushes
- deploy without changing a platform-managed project's source ownership.
+ Returns a fast control-plane health snapshot for the project and its
+ deployed resources. The endpoint does not run live provider probes.
+ A successful request returns 200 even when the project status is
+ `unhealthy`; transport and authorization failures use HTTP errors.
+ operationId: getProjectHealth
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
responses:
'200':
- description: The project's source-of-truth state
+ description: Project health snapshot retrieved
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectSourceExportState'
+ $ref: '#/components/schemas/ProjectHealthResponse'
'401':
description: Unauthorized - invalid or missing token
content:
@@ -1692,7 +1839,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - project not owned by the caller
+ description: Access denied
content:
application/json:
schema:
@@ -1709,42 +1856,19 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '501':
- description: Source export is not available in this deployment mode
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ /projects/{id}/metrics/query:
post:
tags:
- Projects
- - Git Connections
- summary: Initialize an empty repository with a project's stored source
- operationId: exportProjectSource
+ summary: Query project runtime metrics
description: |
- Creates the first commit in the connected repository and pushes it
- directly to the configured production branch. The push enters the
- ordinary Git auto-deploy flow. Direct source writes remain frozen until
- that deployment succeeds and the repository becomes the source of truth.
-
- The caller confirms the production branch shown before export. Starting
- export pins that branch: later GitHub default-branch changes do not
- repoint the project. If the configured branch changed after the caller
- read it, the request fails without exporting so the caller can show and
- confirm the new value.
-
- The response lists what the export could not carry: resources with no
- successful deployment to take source from (`skipped`), and things no
- export can hand back (`omitted`) — migrations, which Volcano stores no
- copy of, and credential-shaped files, which are left for their owner to
- add.
-
- Requires a connected repository with no commits or branches, and runs
- once. Volcano never creates the repository. If GitHub did not confirm
- the push, retrying creates the same commit and adopts it when it already
- reached the repository.
+ Evaluates a batch of named, curated runtime metric queries over one
+ trailing time range. Query IDs correlate each request with its result;
+ raw backend query languages are intentionally not exposed.
+ operationId: queryProjectMetrics
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
requestBody:
@@ -1752,16 +1876,16 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/ExportProjectSourceRequest'
+ $ref: '#/components/schemas/ProjectMetricsQueryRequest'
responses:
- '201':
- description: The initial production-branch commit that was pushed
+ '200':
+ description: Project runtime metric queries evaluated
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectSourceExport'
+ $ref: '#/components/schemas/ProjectMetricsQueryResponse'
'400':
- description: Malformed request body
+ description: Invalid metric query
content:
application/json:
schema:
@@ -1773,38 +1897,13 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - project not owned by the caller
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project not found, or it has no repository connected
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: |
- The source has already been exported, the repository has already
- taken over as the source of truth, the confirmed production branch
- is stale, a function or frontend deployment is still in progress,
- or the project has no stored source to export
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '422':
- description: |
- The repository refused the branch, or its contents cannot be laid
- out as a repository — a stored file that only ever carries
- credentials, or a layout Git auto-deploy would not read back
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '429':
- description: GitHub rate limited the request
+ description: Project not found
content:
application/json:
schema:
@@ -1815,360 +1914,421 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '501':
- description: Source export is not available in this deployment mode
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
'503':
- description: GitHub integration is not configured
+ description: Runtime metrics backend unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ /projects/{id}/logo:
+ get:
tags:
- Projects
- - Git Connections
- summary: Cancel an incomplete source export
- operationId: cancelProjectSourceExport
+ summary: Get the project logo image
description: |
- Restores platform source writes while the project is in
- `git_exporting` or `git_pending`. If Volcano reserved or deployed the
- root commit, export remains consumed and cannot be run again. The
- connected repository and any commit already pushed to it are unchanged.
- security:
- - UserToken: []
+ Returns the raw logo image stored in the project's storage folder. This
+ endpoint is unauthenticated so the asset can be rendered directly in an
+ `
` tag; project IDs are unguessable UUIDs and logos are
+ non-sensitive branding. The `Project.logo_url` field exposes a versioned
+ path to this endpoint.
+ operationId: getProjectLogo
+ security: []
parameters:
- $ref: '#/components/parameters/ProjectId'
responses:
- '204':
- description: The incomplete source transition was canceled
- '401':
- description: Unauthorized - invalid or missing token
+ '200':
+ description: Logo image
content:
- application/json:
+ image/png:
schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project not owned by the caller
- content:
- application/json:
+ type: string
+ format: binary
+ image/jpeg:
schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project not found
- content:
- application/json:
+ type: string
+ format: binary
+ image/gif:
schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: No incomplete transition exists, or Git already took ownership
- content:
- application/json:
+ type: string
+ format: binary
+ image/webp:
schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
- content:
- application/json:
+ type: string
+ format: binary
+ image/svg+xml:
schema:
- $ref: '#/components/schemas/Error'
- '501':
- description: Source export is not available in this deployment mode
+ type: string
+ format: binary
+ '404':
+ description: Project or logo not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/git-connection/production-branch:
- put:
+ post:
tags:
- Projects
- - Git Connections
- summary: Set the branch a project deploys from
+ summary: Upload or replace the project logo
description: |
- Changes only the production branch, leaving the repository binding
- alone. PUT /projects/{id}/git-connection can also set it, but that is a
- full rebind: it needs connection_id, installation_id and a repository
- selector resent, and re-resolves the repository against GitHub for a
- field that does not depend on it.
-
- The branch does not have to exist. It is validated as a Git branch name
- and nothing more, so a project can be pointed at a branch that is about
- to be pushed — the case a repository created empty depends on.
-
- Setting the branch here marks it as the project's own choice, so a later
- default-branch rename on GitHub no longer moves it. Projects that never
- set one keep following the repository's default branch.
- operationId: setProjectGitProductionBranch
+ Uploads an image as the project's logo, storing it in the project's
+ storage folder. Accepts PNG, JPEG, GIF, WebP, or SVG up to 2 MB. Replaces any
+ existing logo. Returns the updated project, whose `logo_url` reflects
+ the new logo.
+ operationId: uploadProjectLogo
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
requestBody:
required: true
content:
- application/json:
+ multipart/form-data:
schema:
- $ref: '#/components/schemas/SetProjectGitProductionBranchRequest'
+ type: object
+ required:
+ - logo
+ properties:
+ logo:
+ type: string
+ format: binary
+ description: Logo image (PNG, JPEG, GIF, WebP, or SVG; max 2 MB)
responses:
'200':
- description: The project's repo connection, with the new branch
+ description: Logo uploaded
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectGitConnection'
+ $ref: '#/components/schemas/Project'
'400':
- description: |
- Malformed request body, or a production_branch that is not a valid
- Git branch name
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ description: Bad request (missing file, unsupported type, or too large)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Project not owned by the caller
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: |
- Project not found, or it has no repository connected. The branch is
- part of the connection, so there is nothing to set it on until one
- exists.
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: A Git source transition is pending
+ description: Project is being deleted
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to set the production branch
+ '503':
+ description: Logo storage is not configured
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/git-connection:
- get:
+ delete:
tags:
- Projects
- - Git Connections
- summary: Get a project's repo connection
- operationId: getProjectGitConnection
+ summary: Remove the project logo
+ description: |
+ Deletes the project logo from the project's storage folder and clears its
+ record. Returns the updated project with no `logo_url`.
+ operationId: deleteProjectLogo
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
responses:
'200':
- description: The project's current repo connection
+ description: Logo removed
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectGitConnection'
- '401':
- description: Unauthorized - invalid or missing token
+ $ref: '#/components/schemas/Project'
+ '403':
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Project not owned by the caller
+ '404':
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found, or has no repo connection
+ '409':
+ description: Project is being deleted
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to get project git connection
+ '503':
+ description: Logo storage is not configured
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- put:
+ /projects/{id}/usage:
+ get:
tags:
- Projects
- - Git Connections
- summary: Connect or update a project's repo connection
+ summary: Get usage metrics for a project
description: |
- Full replace, following Vercel's model: many projects may point at the
- same repo, so this only binds the project — it never creates or
- deletes git-provider state. Used for both the initial connect and
- later edits (repo change, root directory, production branch).
- Resolves the repository_id or repo_full_name selector against the repos
- accessible through installation_id via connection_id's stored GitHub
- user token, then persists repository metadata only from that validated
- GitHub response.
- operationId: connectProjectGit
+ Returns project usage totals for the current usage month plus
+ recent hourly and daily time series for each tracked metric.
+ operationId: getProjectUsage
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ConnectProjectGitRequest'
responses:
'200':
- description: The project's repo connection
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectGitConnection'
- '400':
- description: |
- Malformed request body, no repository selector, selectors that
- identify different repositories, a production_branch that is not a
- valid Git branch name, no production_branch on a repository with no
- default branch to follow (name one to connect a repository that has
- no commits yet), or a production_branch other than the new
- repository's default in a request that also changes repository.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ description: Usage metrics retrieved
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/ProjectUsageResponse'
'403':
- description: |
- Project not owned by the caller, or the selected repository is not
- accessible through installation_id
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project or connection not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: |
- A Git source transition is pending or complete, so the recorded
- repository and root cannot be changed
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to connect project git
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Git provider integration is not configured
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- delete:
+ /projects/{id}/shared-variables:
+ put:
tags:
- Projects
- - Git Connections
- summary: Disconnect a project's repo connection
- operationId: disconnectProjectGit
+ summary: Replace shared variable names
+ description: |
+ Atomically replaces the complete shared function-variable list without
+ changing values. Names must already exist. Validates final affected
+ function environments before membership or propagation side effects.
+ An empty list clears membership. Omitted names remain stored as non-shared variables.
+ operationId: replaceSharedVariables
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties: false
+ required:
+ - shared_variables
+ not:
+ required:
+ - expected_shared_variables
+ - expected_shared_variables_digest
+ properties:
+ shared_variables:
+ type: array
+ uniqueItems: true
+ items:
+ type: string
+ maxLength: 256
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ expected_shared_variables:
+ type: array
+ uniqueItems: true
+ description: When present, replace only if the current complete shared list matches this list.
+ items:
+ type: string
+ maxLength: 256
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ expected_shared_variables_digest:
+ type: string
+ minLength: 64
+ maxLength: 64
+ pattern: ^[a-f0-9]{64}$
+ description: SHA-256 of the sorted unique current shared names joined by a newline. Use instead of expected_shared_variables for a compact conditional replacement.
responses:
'204':
- description: Connection removed
+ description: Shared list replaced and affected function synchronization started.
+ '400':
+ description: Invalid names or final function environment.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'401':
- description: Unauthorized - invalid or missing token
+ description: Unauthorized
+ '404':
+ description: Project not found
+ '409':
+ description: Shared list changed since it was read.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Project not owned by the caller
+ '413':
+ description: Request body exceeds 4,194,304 bytes
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found, or has no repo connection
+ '500':
+ description: Persistence or synchronization failed
+ '503':
+ description: Shared variable membership writes are disabled during rollout
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/frontend-shared-variables:
+ put:
+ tags:
+ - Projects
+ summary: Replace frontend shared variable names
+ description: |
+ Atomically replaces the complete shared frontend-variable list without
+ changing values. Names must already exist. Validates final affected
+ frontend environments before membership or propagation side effects.
+ An empty list clears membership. Omitted names remain stored outside the frontend shared list.
+ operationId: replaceFrontendSharedVariables
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties: false
+ required:
+ - frontend_shared_variables
+ not:
+ required:
+ - expected_frontend_shared_variables
+ - expected_frontend_shared_variables_digest
+ properties:
+ frontend_shared_variables:
+ type: array
+ uniqueItems: true
+ items:
+ type: string
+ maxLength: 256
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ expected_frontend_shared_variables:
+ type: array
+ uniqueItems: true
+ description: When present, replace only if the current complete frontend shared list matches this list.
+ items:
+ type: string
+ maxLength: 256
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ expected_frontend_shared_variables_digest:
+ type: string
+ minLength: 64
+ maxLength: 64
+ pattern: ^[a-f0-9]{64}$
+ description: SHA-256 of the sorted unique current shared names joined by a newline. Use instead of expected_frontend_shared_variables for a compact conditional replacement.
+ responses:
+ '204':
+ description: Frontend shared list replaced and affected frontend synchronization started.
+ '400':
+ description: Invalid names or final frontend environment.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized
+ '404':
+ description: Project not found
'409':
- description: |
- A Git source transition is pending or complete, so the recorded
- repository cannot be disconnected
+ description: Frontend shared list changed since it was read.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to disconnect project git
+ '413':
+ description: Request body exceeds 4,194,304 bytes
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/git-deploy-settings:
+ '500':
+ description: Persistence or synchronization failed
+ /projects/{id}/config:
get:
tags:
- Projects
- - Git Connections
- summary: Get a project's Git auto-deploy settings
- operationId: getProjectGitDeploySettings
+ summary: Export project configuration
+ description: |
+ Exports the project's current user-facing configuration as a
+ declarative manifest. Returns JSON by default. Request the canonical
+ volcano-config.yaml rendering with `Accept: application/yaml` or
+ `?format=yaml`; the YAML is returned verbatim as the raw response body
+ (`Content-Type: application/yaml`) and is meant to be saved as-is.
+ Variable values and write-only secrets (SMTP password, OAuth client secrets, TLS material)
+ are omitted from the export; shared_variables and frontend_shared_variables contain names only; the YAML rendering adds a header comment
+ describing how to set them via CLI environment interpolation.
+ operationId: getProjectConfig
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
+ - name: format
+ in: query
+ required: false
+ description: Response format override. Takes precedence over the Accept header.
+ schema:
+ type: string
+ enum:
+ - json
+ - yaml
responses:
'200':
- description: The project's current Git auto-deploy settings
+ description: |
+ Current project configuration. JSON by default; when YAML is
+ requested the body is the canonical volcano-config.yaml document
+ served verbatim with `Content-Type: application/yaml`.
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectGitDeploySettings'
+ $ref: '#/components/schemas/ProjectConfig'
+ application/yaml:
+ schema:
+ type: string
+ format: binary
+ description: |
+ Canonical volcano-config.yaml document returned verbatim,
+ ready to be saved as-is. The response body is limited to
+ 4,194,304 bytes, matching the configuration apply limit.
'401':
description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Project not owned by the caller
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
'404':
description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to get project git deploy settings
+ '413':
+ description: Canonical YAML export exceeds 4,194,304 bytes
content:
application/json:
schema:
@@ -2176,40 +2336,52 @@ paths:
put:
tags:
- Projects
- - Git Connections
- summary: Update a project's Git auto-deploy settings
+ summary: Apply project configuration
description: |
- Full replace of the project's Git auto-deploy settings: what a push to
- the connected repo's production branch deploys.
-
- Connecting a repository sets auto_deploy_enabled and deploy_functions
- to true for a project that has never called this endpoint, so a push
- deploys without any further setup. Once these settings have been saved
- here they are the project's own: connecting, rebinding, disconnecting
- and reconnecting all leave them untouched, including when they were
- saved before any repository was connected. Frontend settings are off
- until set here; the frontend need not exist when they are saved, since
- frontend_name is resolved at deploy time.
- operationId: updateProjectGitDeploySettings
+ Validates and applies a declarative configuration manifest to the
+ project, reconciling each declared section and returning a per-resource
+ report. Omitted sections are untouched. Declared collection keys
+ (`variables`, `buckets[].policies`, `auth.providers.oauth`,
+ `auth.email.templates`, `functions[].schedulers`) are fully synced:
+ resources absent from the manifest are deleted. Functions, frontends,
+ databases, and buckets are never created or deleted; manifest entries
+ for resources that do not exist are skipped and reported in `skipped`,
+ and existing resources missing from a declared section are reported in
+ `missing`. Validation failures (including plan-gate violations) return
+ 422 and nothing is applied. Set `dry_run=true` to get the projected
+ report without applying changes. Applies are serialized per project;
+ a concurrent apply returns 409.
+ operationId: applyProjectConfig
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
+ - name: dry_run
+ in: query
+ required: false
+ description: Validate and report projected actions without applying changes.
+ schema:
+ type: boolean
+ default: false
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateProjectGitDeploySettingsRequest'
+ $ref: '#/components/schemas/ProjectConfig'
responses:
'200':
- description: The project's updated Git auto-deploy settings
+ description: |
+ Apply report. Individual entries may still carry `action: error`
+ for apply-phase failures (summary.errors > 0); already-applied
+ changes are not rolled back.
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectGitDeploySettings'
+ $ref: '#/components/schemas/ProjectConfigApplyResult'
'400':
- description: Malformed request body, or frontend_app_root without frontend_name
+ description: Malformed request body
content:
application/json:
schema:
@@ -2220,12 +2392,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Project not owned by the caller
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
'404':
description: Project not found
content:
@@ -2233,57 +2399,59 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: |
- A Git source transition is pending, or this change would remove
- deploy coverage after Git has taken over
+ description: Another apply is in progress for this project
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to update project git deploy settings
+ '422':
+ description: Manifest validation failed; nothing was applied
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectConfigValidationErrorResponse'
+ '503':
+ description: Shared variable membership writes are disabled during rollout
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/queries:
+ /projects/{id}/source-export:
get:
tags:
- - Databases
- summary: Get database queries
+ - Projects
+ - Git Connections
+ summary: Report the project's source-of-truth state
+ operationId: getProjectSourceExport
description: |
- Returns the database's current top queries from pg_stat_statements
- ranked by total execution time.
+ Volcano stores the source of the functions and frontend it runs for a
+ project. This reports whether that source has been written to the
+ connected repository, and whether the repository has taken over as the
+ project's source of truth.
- **PRO plan required.** This endpoint is only available to projects owned
- by users on the PRO billing plan.
- operationId: getProjectDatabaseQueries
+ `mode` is `platform`, `git_exporting`, `git_pending`, or `git`. Export
+ enters `git_exporting` before reading stored source. GitHub's signed
+ push event confirms that the initial commit reached the production
+ branch. That push or a newer production push changes the mode to
+ `git_pending` when it starts a deployment. `exported_at` records that
+ transition.
+
+ A successful Git run completes the transition when it matches the
+ recorded repository, production branch, and root directory and actually
+ dispatches every recorded resource. Ordinary production-branch pushes
+ deploy without changing a platform-managed project's source ownership.
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - name: limit
- in: query
- description: Maximum number of queries to return.
- schema:
- type: integer
- minimum: 1
- maximum: 100
- default: 10
responses:
'200':
- description: Database query performance retrieved
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseQueryPerformanceResponse'
- '400':
- description: Bad request - invalid query parameters
+ description: The project's source-of-truth state
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/ProjectSourceExportState'
'401':
description: Unauthorized - invalid or missing token
content:
@@ -2291,13 +2459,13 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Access denied
+ description: Forbidden - project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project or database not found
+ description: Project not found
content:
application/json:
schema:
@@ -2308,86 +2476,60 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /deployments:
- get:
+ '501':
+ description: Source export is not available in this deployment mode
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ post:
tags:
- Projects
- summary: List deployments across a user's projects
+ - Git Connections
+ summary: Initialize an empty repository with a project's stored source
+ operationId: exportProjectSource
description: |
- Lists Function and Frontend deployment attempts across every project the
- user owns, newest first. Pass `project_id` to narrow the feed to a single
- project.
-
- Scope is project **ownership** (`projects.user_id`). `owner_id` names
- whose deployments to return, not who started them — the actor is
- `initiated_by_user_id`, which this endpoint does not filter on.
-
- With a user token the scope is always the authenticated user: `owner_id`
- may be omitted, or set to that same user, but naming anyone else is
- refused with 403. Service callers on the management API must pass it,
- since they have no authenticated user.
-
- The owner is not checked for existence: an id with no projects returns an
- empty page rather than `404`. Unlike `/users/{id}/usage`, this endpoint is
- polled to detect an event, so a caller needs `404` to keep meaning "this
- route is not served here" — which is how a consumer notices it is running
- against an older release. A mistyped owner therefore reads as "nothing
- deployed"; callers that need to tell those apart should verify the user
- through `GET /users/{id}` first.
+ Creates the first commit in the connected repository and pushes it
+ directly to the configured production branch. The push enters the
+ ordinary Git auto-deploy flow. Direct source writes remain frozen until
+ that deployment succeeds and the repository becomes the source of truth.
- Ordering is selectable. The default is the feed order — most recent
- attempt first. `completed_at.asc` orders by completion, oldest first, and
- considers only attempts that finished; combined with `limit=1` and a
- `status` filter it answers "when did this user first succeed" in one
- bounded query.
+ The caller confirms the production branch shown before export. Starting
+ export pins that branch: later GitHub default-branch changes do not
+ repoint the project. If the configured branch changed after the caller
+ read it, the request fails without exporting so the caller can show and
+ confirm the new value.
- Both pagination modes are supported, selected exactly as
- `/projects/{id}/deployments` selects them: `cursor`/`ending_before` (or a
- `limit` with no `page`) uses keyset pagination; otherwise `page`/`limit`
- offset pagination. `page` with a cursor, and `cursor` with
- `ending_before`, are rejected.
+ The response lists what the export could not carry: resources with no
+ successful deployment to take source from (`skipped`), and things no
+ export can hand back (`omitted`) — migrations, which Volcano stores no
+ copy of, and credential-shaped files, which are left for their owner to
+ add.
- A cursor is bound to every filter *and* to `order`, so changing any of
- them mid-pagination rejects the cursor rather than silently skipping or
- repeating rows. The keyset position is `(created_at, id)` for
- `created_at.desc` and `(completed_at, id)` for `completed_at.asc`.
- operationId: listDeployments
+ Requires a connected repository with no commits or branches, and runs
+ once. Volcano never creates the repository. If GitHub did not confirm
+ the push, retrying creates the same commit and adopts it when it already
+ reached the repository.
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/DeploymentOwnerId'
- - name: project_id
- in: query
- required: false
- description: Restrict the feed to a single project owned by the user.
- schema:
- type: string
- format: uuid
- - name: created_after
- in: query
- required: false
- description: Restrict results to attempts created at or after this timestamp.
- schema:
- type: string
- format: date-time
- - $ref: '#/components/parameters/DeploymentResourceType'
- - $ref: '#/components/parameters/DeploymentStatus'
- - $ref: '#/components/parameters/DeploymentOperation'
- - $ref: '#/components/parameters/DeploymentOrder'
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ExportProjectSourceRequest'
responses:
- '200':
- description: Successful response
+ '201':
+ description: The initial production-branch commit that was pushed
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedProjectDeployments'
+ $ref: '#/components/schemas/ProjectSourceExport'
'400':
- description: Bad request - invalid filter or pagination
+ description: Malformed request body
content:
application/json:
schema:
@@ -2399,147 +2541,99 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - owner_id names a different user
+ description: Forbidden - project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '404':
+ description: Project not found, or it has no repository connected
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/deployments:
- get:
- tags:
- - Projects
- summary: List deployments in a project
- description: |
- Lists Function and Frontend deployment attempts across the project,
- ordered most-recent first. Each item includes a normalized resource
- reference so clients can render both resource types without extra
- fetches.
- operationId: listProjectDeployments
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
- - name: created_after
- in: query
- required: false
- description: Restrict results to attempts created at or after this timestamp.
- schema:
- type: string
- format: date-time
- - name: resource_type
- in: query
- required: false
+ '409':
description: |
- Restrict the feed to a single resource type. Omit to return both
- Function and Frontend deployments.
- schema:
- type: string
- enum:
- - function
- - frontend
- responses:
- '200':
- description: Successful response
+ The source has already been exported, the repository has already
+ taken over as the source of truth, the confirmed production branch
+ is stale, a function or frontend deployment is still in progress,
+ or the project has no stored source to export
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedProjectDeployments'
- '400':
- description: Bad request - invalid identifier
+ $ref: '#/components/schemas/Error'
+ '422':
+ description: |
+ The repository refused the branch, or its contents cannot be laid
+ out as a repository — a stored file that only ever carries
+ credentials, or a layout Git auto-deploy would not read back
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ '429':
+ description: GitHub rate limited the request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '501':
+ description: Source export is not available in this deployment mode
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/deployments/summary:
- get:
+ '503':
+ description: GitHub integration is not configured
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ delete:
tags:
- Projects
- summary: Summarize deployments in a project
+ - Git Connections
+ summary: Cancel an incomplete source export
+ operationId: cancelProjectSourceExport
description: |
- Summarizes deployment attempts for one comparable resource pipeline.
- Success rate uses conclusive outcomes only: active and deleted attempts
- are successful; failed and degraded attempts are failures; in-progress
- and superseded attempts are excluded. Median build duration includes
- completed, non-superseded attempts with recorded build work,
- including failed builds.
- operationId: summarizeProjectDeployments
+ Restores platform source writes while the project is in
+ `git_exporting` or `git_pending`. If Volcano reserved or deployed the
+ root commit, export remains consumed and cannot be run again. The
+ connected repository and any commit already pushed to it are unchanged.
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: search
- in: query
- required: false
- description: Restrict the summary to resource names containing this value.
- schema:
- type: string
- - name: resource_type
- in: query
- required: true
- description: Restrict the summary to one comparable deployment pipeline.
- schema:
- type: string
- enum:
- - function
- - frontend
- - name: created_after
- in: query
- required: false
- description: Restrict results to attempts created at or after this timestamp.
- schema:
- type: string
- format: date-time
responses:
- '200':
- description: Successful response
+ '204':
+ description: The incomplete source transition was canceled
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectDeploymentSummary'
- '400':
- description: Bad request - invalid identifier or filter
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ '404':
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ '409':
+ description: No incomplete transition exists, or Git already took ownership
content:
application/json:
schema:
@@ -2550,36 +2644,55 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/domains:
- get:
+ '501':
+ description: Source export is not available in this deployment mode
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/git-connection/production-branch:
+ put:
tags:
- - Frontends
- summary: List all custom domains in a project
+ - Projects
+ - Git Connections
+ summary: Set the branch a project deploys from
description: |
- Project-scoped custom-domain list. Returns every active custom
- domain across every frontend in the project (excludes soft-deleted
- rows). Each item inlines the linked frontend's id and name so the
- UI does not need a second fetch to render the "Linked to" column.
- operationId: listProjectCustomDomains
+ Changes only the production branch, leaving the repository binding
+ alone. PUT /projects/{id}/git-connection can also set it, but that is a
+ full rebind: it needs connection_id, installation_id and a repository
+ selector resent, and re-resolves the repository against GitHub for a
+ field that does not depend on it.
+
+ The branch does not have to exist. It is validated as a Git branch name
+ and nothing more, so a project can be pointed at a branch that is about
+ to be pushed — the case a repository created empty depends on.
+
+ Setting the branch here marks it as the project's own choice, so a later
+ default-branch rename on GitHub no longer moves it. Projects that never
+ set one keep following the repository's default branch.
+ operationId: setProjectGitProductionBranch
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SetProjectGitProductionBranchRequest'
responses:
'200':
- description: Successful response
+ description: The project's repo connection, with the new branch
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedProjectCustomDomains'
+ $ref: '#/components/schemas/ProjectGitConnection'
'400':
- description: Bad request - invalid identifier
+ description: |
+ Malformed request body, or a production_branch that is not a valid
+ Git branch name
content:
application/json:
schema:
@@ -2591,392 +2704,174 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - project ownership required
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '404':
+ description: |
+ Project not found, or it has no repository connected. The branch is
+ part of the connection, so there is nothing to set it on until one
+ exists.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/functions:
- get:
- tags:
- - Functions
- summary: List all functions in a project
- description: |
- Supports two mutually exclusive pagination modes. Offset mode uses `page`
- and `limit` and returns `next` (URL). Cursor mode uses `cursor` and
- `limit`, supports `search` (case-insensitive name match), and returns
- `next_cursor`/`prev_cursor`. Sending both `page` and `cursor` (or `page`
- and `search`) returns 400.
- operationId: listFunctions
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
- responses:
- '200':
- description: Successful response
+ '409':
+ description: A Git source transition is pending
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedFunctions'
- '404':
- description: Project not found
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Failed to set the production branch
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- post:
+ /projects/{id}/git-connection:
+ get:
tags:
- - Functions
- summary: Create or update function code
- description: |
- Upload a serverless function source bundle. Direct API clients may send the function code
- as a ZIP or tar.gz archive via multipart/form-data. The API stores a normalized tar.gz
- source archive.
- Cloud deploys should include source files and dependency manifests/lockfiles, not installed
- dependency directories. Volcano installs Node.js, Python, and Ruby dependencies during the
- function compile build.
- Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
- does not apply its own source archive size limit. After the final container image is
- built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
- Uploaded source archives cannot contain symlink entries. Safe symlinks created during
- the cloud build are materialized before publish.
- Volcano builds and deploys the function asynchronously after upload. A deployment that starts
- immediately returns a Function resource with `status: provisioning`, then transitions to
- `active` or `failed`. If another deployment is running, the response preserves the resource's
- current status and exposes the queued deployment through `pending_deployment_id`.
- Existing function traffic continues to use the last known-good runtime during an update. A failed
- update keeps that runtime available and records the attempted deployment as failed.
- Only one deployment runs for a given function. A newer request supersedes any queued request
- and starts after the running deployment. Different functions and projects deploy concurrently.
- If a function with the same name already exists in the project, this operation updates that
- function's runtime, handler, and source bundle and returns `200 OK`.
- Each project can contain up to 10,000 functions. Creating a new function over this cap returns 403.
- operationId: createFunction
+ - Projects
+ - Git Connections
+ summary: Get a project's repo connection
+ operationId: getProjectGitConnection
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- required:
- - name
- - code
- - runtime
- properties:
- name:
- type: string
- description: DNS-safe function name (lowercase letters, numbers, hyphens; cannot start or end with hyphen)
- maxLength: 63
- pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
- example: my-api-function
- code:
- type: string
- format: binary
- description: ZIP or tar.gz archive containing function source code plus dependency manifests/lockfiles. The API enforces SOURCE_ARCHIVE_SIZE_LIMIT_MB and stores a normalized tar.gz source archive.
- runtime:
- type: string
- enum:
- - nodejs22.x
- - nodejs24.x
- - python3.10
- - python3.11
- - python3.12
- - python3.13
- - python3.14
- - ruby3.3
- - ruby3.4
- - ruby4.0
- description: |
- Runtime environment. Required.
- - Node.js: nodejs22.x, nodejs24.x
- - Python: python3.10, python3.11, python3.12, python3.13, python3.14
- - Ruby: ruby3.3, ruby3.4, ruby4.0
- example: nodejs24.x
- handler:
- type: string
- description: |
- The name of the function to invoke. Defaults to "handler" if not specified.
- Your code must export/define a function with this name:
- - Node.js: exports.handler (in index.js)
- - Python: def handler() (in main.py)
- - Ruby: def handler() (in main.rb)
- default: handler
- example: handler
- is_public:
- type: boolean
- description: |
- Whether the function can be reached through public invocation
- ingress. Omit it to keep the function's current visibility; a
- new function starts private.
- invocation_mode:
- $ref: '#/components/schemas/FunctionInvocationMode'
- http_auth_mode:
- $ref: '#/components/schemas/FunctionHTTPAuthMode'
- openapi_spec:
- type: string
- description: JSON-encoded OpenAPI 3.0 or 3.1 metadata for an HTTP-mode function.
- variable_scope:
- type: string
- enum:
- - all
- - scoped
- description: |
- Which project variables this function receives. `all` (the default) gives it only project variables marked `shared: true`; `scoped` gives it only the variables it selects. Omitting this leaves an existing function's scope unchanged.
- variables:
- type: string
- description: |
- JSON-encoded array of project variable names this function requires, on top of the ones detected in its source. A declared name the project does not define is rejected with 400; a detected name it does not define is ignored. Only used when `variable_scope` is `scoped`. Omitting this leaves an existing function's declared names unchanged.
responses:
'200':
- description: Existing function updated; its deployment was started or queued
+ description: The project's current repo connection
content:
application/json:
schema:
- $ref: '#/components/schemas/Function'
- '201':
- description: Function created and deployment workflow started
+ $ref: '#/components/schemas/ProjectGitConnection'
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- $ref: '#/components/schemas/Function'
- '400':
- description: Bad request (invalid file, too large, etc.)
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Function limit exceeded for the project
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: |
- Function deletion is queued or running, or the name is already held
- by a durable function — a function cannot change kind.
+ '404':
+ description: Project not found, or has no repo connection
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
- description: Internal server error (function deployment failed)
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/functions/{functionId}:
- get:
- tags:
- - Functions
- summary: Get function by ID
- operationId: getFunction
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FunctionId'
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Function'
- '404':
- description: Function not found
+ description: Failed to get project git connection
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- patch:
+ put:
tags:
- - Functions
- summary: Update function settings
- operationId: updateFunction
+ - Projects
+ - Git Connections
+ summary: Connect or update a project's repo connection
+ description: |
+ Full replace, following Vercel's model: many projects may point at the
+ same repo, so this only binds the project — it never creates or
+ deletes git-provider state. Used for both the initial connect and
+ later edits (repo change, root directory, production branch).
+ Resolves the repository_id or repo_full_name selector against the repos
+ accessible through installation_id via connection_id's stored GitHub
+ user token, then persists repository metadata only from that validated
+ GitHub response.
+ operationId: connectProjectGit
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FunctionId'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateFunctionRequest'
- examples:
- makePublic:
- summary: Make function public (anon keys can invoke)
- value:
- is_public: true
- makePrivate:
- summary: Make function private (default behavior)
- value:
- is_public: false
+ $ref: '#/components/schemas/ConnectProjectGitRequest'
responses:
'200':
- description: Function updated
+ description: The project's repo connection
content:
application/json:
schema:
- $ref: '#/components/schemas/Function'
+ $ref: '#/components/schemas/ProjectGitConnection'
'400':
- description: Bad request
+ description: |
+ Malformed request body, no repository selector, selectors that
+ identify different repositories, a production_branch that is not a
+ valid Git branch name, no production_branch on a repository with no
+ default branch to follow (name one to connect a repository that has
+ no commits yet), or a production_branch other than the new
+ repository's default in a request that also changes repository.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Function not found
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
- tags:
- - Functions
- summary: Delete a function
- description: |
- Schedules asynchronous function deletion. If another deployment is running, the function
- preserves its current status and exposes the queued deletion through `pending_deployment_id`.
- Its status changes to `deleting` when cleanup starts. After cleanup, it returns 404 and no
- longer appears in function lists.
- operationId: deleteFunction
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FunctionId'
- responses:
- '202':
- description: Function deletion started or queued
- '404':
- description: Function not found
+ '403':
+ description: |
+ Project not owned by the caller, or the selected repository is not
+ accessible through installation_id
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /functions/{functionId}/invoke:
- post:
- tags:
- - Functions
- summary: Invoke a function
- description: |
- Invoke a serverless function.
-
- **With Service Key** (admin/background operations):
- - Use for background jobs, webhooks, cron, admin operations
- - Function receives payload only (no user context)
- - Database queries bypass RLS (admin access)
-
- **With Auth User Token** (user-facing):
- - Use for user-initiated actions
- - Function receives payload + `__volcano_auth` context:
- ```javascript
- {
- user_id: "uuid",
- email: "user@example.com",
- project_id: "uuid",
- role: "authenticated" or "anonymous"
- }
- ```
- - Database queries enforce RLS (user-scoped data)
-
- **With Anon Key** (public function only):
- - Requires anon key permission: `functions.invoke`
- - Function must have `is_public: true`
- - Function receives payload only (no `__volcano_auth`)
-
- **Transport and CORS:**
- - This operation is the authenticated direct RPC endpoint and always uses the
- POST `{payload: ...}` contract, including for functions whose DNS ingress is
- configured in HTTP mode.
- - The geo-routed DNS ingress is `https://{functionId}.functions./`.
- - RPC-mode DNS ingress accepts POST at `/`. HTTP-mode DNS ingress accepts GET,
- HEAD, POST, PUT, PATCH, and DELETE at `/` and nested paths.
- - Direct and RPC-mode CORS preflight advertises `POST, OPTIONS`. HTTP-mode DNS
- preflight advertises `GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS`.
- - `http_auth_mode: none` applies only to public HTTP-mode DNS ingress; this
- direct operation always requires a Volcano credential.
-
- **Durable functions are not invocable here.** A durable function's id
- answers 404, whatever its visibility, because a synchronous call would
- run it with no execution record, no idempotency and no concurrency
- accounting. Start one with
- `POST /durable-functions/{functionId}/executions`.
- operationId: invokeFunction
- security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
- parameters:
- - $ref: '#/components/parameters/FunctionId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/FunctionInvocationRequest'
- examples:
- serviceCall:
- summary: Admin/background operation (service key)
- value:
- payload:
- action: process_batch
- items:
- - 1
- - 2
- - 3
- userCall:
- summary: User-initiated call (auth token)
- value:
- payload:
- action: get_profile
- anonCall:
- summary: Public function call (anon key)
- value:
- payload:
- action: ping_public_endpoint
- responses:
- '200':
- description: Function response (passthrough from function runtime)
- headers:
- X-Volcano-Version:
- description: Volcano API/runtime version that served this invocation (`` in production, `-` in non-production)
+ '404':
+ description: Project or connection not found
+ content:
+ application/json:
schema:
- type: string
- X-Volcano-Region:
- description: Region the function ran in (for example `us-east-1`)
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: |
+ A Git source transition is pending or complete, so the recorded
+ repository and root cannot be changed
+ content:
+ application/json:
schema:
- type: string
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Failed to connect project git
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionInvocationResponse'
- '400':
- description: Bad request - invalid payload or function in failed state
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Git provider integration is not configured
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ delete:
+ tags:
+ - Projects
+ - Git Connections
+ summary: Disconnect a project's repo connection
+ operationId: disconnectProjectGit
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '204':
+ description: Connection removed
'401':
description: Unauthorized - invalid or missing token
content:
@@ -2984,237 +2879,183 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - CORS blocked, missing `functions.invoke`, or private function with anon key
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Function not found
+ description: Project not found, or has no repo connection
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
+ '409':
description: |
- Rate limit exceeded (per-function or project-wide limit), or the
- owning platform user's billing-cycle bandwidth allowance (aggregate ingress +
- egress) was exceeded.
+ A Git source transition is pending or complete, so the recorded
+ repository cannot be disconnected
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: |
- Function still provisioning or rate limiting service unavailable.
- A freshly deployed (or updated) function may briefly report
- `provisioning` and reject invocations until the background status
- reconciler observes its deployment workflow completing and transitions
- it to `active`. This is expected for a few seconds after deploy; clients
- should retry.
+ '500':
+ description: Failed to disconnect project git
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- default:
- description: Function response (passthrough; status code/body/headers come from the function)
- headers:
- X-Volcano-Version:
- description: Volcano API/runtime version that served this invocation (`` in production, `-` in non-production)
- schema:
- type: string
- X-Volcano-Region:
- description: Region the function ran in (for example `us-east-1`)
+ /projects/{id}/git-deploy-settings:
+ get:
+ tags:
+ - Projects
+ - Git Connections
+ summary: Get a project's Git auto-deploy settings
+ operationId: getProjectGitDeploySettings
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '200':
+ description: The project's current Git auto-deploy settings
+ content:
+ application/json:
schema:
- type: string
+ $ref: '#/components/schemas/ProjectGitDeploySettings'
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionInvocationResponse'
- /durable-functions/{functionId}/executions:
- post:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Project not owned by the caller
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Failed to get project git deploy settings
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ put:
tags:
- - Durable Functions
- summary: Start a durable execution from an application
+ - Projects
+ - Git Connections
+ summary: Update a project's Git auto-deploy settings
description: |
- Starts an execution of a durable function using an application
- credential, and returns its handle.
-
- This is the durable counterpart of `POST /functions/{functionId}/invoke`,
- and it is the endpoint an application calls. Like that one, it is not
- project-scoped: an anon key, a service key and an auth user token each
- carry their own project. The project-scoped collection under
- `/projects/{id}/durable-functions/...` remains the owner's management
- surface.
-
- **With a service key or an auth user token:** any durable function in
- the project.
-
- **With an anon key:** requires the `functions.invoke` permission, and
- the function must have `is_public: true`.
-
- Starting is all this endpoint does. Reading a result or stopping an
- execution requires the project owner's token, because an anon key is
- shared by everyone who loads the page and an execution is addressed by
- id alone.
-
- Send `X-Volcano-Execution-Name` to make the start idempotent: repeating
- a start with the same name returns the existing execution instead of
- beginning a second one.
+ Full replace of the project's Git auto-deploy settings: what a push to
+ the connected repo's production branch deploys.
- Each execution counts once against the project's durable execution
- allowance, however many times the start is retried under the same
- execution name, and the number in flight at once is capped by the plan.
- The operations the execution performs are counted against the durable
- operations allowance when it finishes.
- operationId: startDurableExecutionFromApplication
+ Connecting a repository sets auto_deploy_enabled and deploy_functions
+ to true for a project that has never called this endpoint, so a push
+ deploys without any further setup. Once these settings have been saved
+ here they are the project's own: connecting, rebinding, disconnecting
+ and reconnecting all leave them untouched, including when they were
+ saved before any repository was connected. Frontend settings are off
+ until set here; the frontend need not exist when they are saved, since
+ frontend_name is resolved at deploy time.
+ operationId: updateProjectGitDeploySettings
security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - $ref: '#/components/parameters/DurableFunctionId'
- - name: X-Volcano-Execution-Name
- in: header
- required: false
- description: |
- Idempotency key for this execution. Generated when omitted. A repeat
- under a name that already names a running execution returns that
- execution and is not charged again.
-
- Letters, digits, `-`, `_` and `.`, up to 255 characters. Anything
- else is rejected with `400`.
- schema:
- type: string
- maxLength: 255
- pattern: ^[A-Za-z0-9._-]+$
+ - $ref: '#/components/parameters/ProjectId'
requestBody:
- required: false
+ required: true
content:
application/json:
schema:
- description: Input passed to the function, up to 256 KiB.
+ $ref: '#/components/schemas/UpdateProjectGitDeploySettingsRequest'
responses:
- '202':
- description: Execution accepted and started
+ '200':
+ description: The project's updated Git auto-deploy settings
content:
application/json:
schema:
- $ref: '#/components/schemas/DurableExecution'
+ $ref: '#/components/schemas/ProjectGitDeploySettings'
'400':
- description: Payload is not valid JSON, or the execution name is invalid
+ description: Malformed request body, or frontend_app_root without frontend_name
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: |
- Missing or invalid credential. Also returned for a platform user
- token, which is not an application credential; project owners start
- executions through the project-scoped collection.
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: |
- The anon key lacks `functions.invoke`, the function is not public,
- or the request's origin is refused by the project's CORS policy.
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: |
- Durable function not found. Also returned for a standard function's
- id and for a durable function in another project, so the response
- cannot be used to tell those apart.
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: |
- Function is not deployed yet, or has no deployed region. Also
- returned when two starts under the same execution name raced and
- both released it, which is retryable as it stands.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '413':
- description: Payload is larger than 256 KiB
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '429':
- description: |
- The project has too many executions in flight for its plan, the
- function invocation rate limit was exceeded, the project is over
- its bandwidth cap, or the account is out of one of its
- billing-cycle durable allowances: executions, operations, or
- compute. Operations and compute are counted once an execution
- finishes, so a refusal on either never interrupts an execution
- already running — it declines the next start.
+ A Git source transition is pending, or this change would remove
+ deploy coverage after Git has taken over
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: |
- Durable execution is not available in this environment, or the
- usage limit service could not be reached to charge the start. The
- first is returned by a deployment that has no durable execution
- engine, such as a local one, and is not retryable there; the second
- is transient.
+ '500':
+ description: Failed to update project git deploy settings
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /functions/resolve:
+ /projects/{id}/databases/{databaseName}/queries:
get:
tags:
- - Functions
- summary: Resolve function name for invocation
+ - Databases
+ summary: Get database queries
description: |
- Resolves a DNS-safe function name to its function ID within the caller's project.
-
- SDKs use this endpoint internally to invoke by function name while routing by function ID.
-
- **With Service Key**:
- - Allowed
-
- **With Auth User Token**:
- - Allowed
+ Returns the database's current top queries from pg_stat_statements
+ ranked by total execution time.
- **With Anon Key**:
- - Requires anon key permission: `functions.invoke`
- - Function must have `is_public: true`
- operationId: resolveFunctionForInvocation
+ **PRO plan required.** This endpoint is only available to projects owned
+ by users on the PRO billing plan.
+ operationId: getProjectDatabaseQueries
security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - name: name
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DatabaseName'
+ - name: limit
in: query
- required: true
+ description: Maximum number of queries to return.
schema:
- type: string
- maxLength: 63
- pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
- description: DNS-safe function name (lowercase letters, numbers, hyphens; cannot start or end with hyphen)
- example: my-function
+ type: integer
+ minimum: 1
+ maximum: 100
+ default: 10
responses:
'200':
- description: Function resolved successfully
+ description: Database query performance retrieved
content:
application/json:
schema:
- $ref: '#/components/schemas/ResolveFunctionResponse'
+ $ref: '#/components/schemas/DatabaseQueryPerformanceResponse'
'400':
- description: Bad request - missing or invalid function name
+ description: Bad request - invalid query parameters
content:
application/json:
schema:
@@ -3226,57 +3067,105 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - CORS blocked or missing `functions.invoke` permission for anon key
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: |
- Function not found (or private function with anon key). A durable
- function is never resolvable here: it is started through
- `POST /durable-functions/{functionId}/executions`, not invoked.
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/logs/activity:
- post:
- tags:
- - Logs
- summary: Get project log activity
- description: |
- Retrieve bucketed log counts for one resource type in the project. Set
- `resource.type` to `function`, `frontend`, or `database`. Add
- `resource.ids` to filter to one or more resources, and add
- `resource.deployments.ids` to count deployment logs instead of runtime
- logs for functions and frontends. Deployment logs are not supported for
- databases. Database logs are a PRO-plan feature; `resource.type=database`
- from a FREE-plan project owner returns 403. The activity window is limited
- to the plan's retention window (FREE: 1 day, PRO: 30 days); older start
- times are clamped to that window.
- operationId: getProjectLogActivity
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/LogActivityRequest'
- responses:
- '200':
- description: Successful response
+ '500':
+ description: Internal server error
content:
application/json:
schema:
- $ref: '#/components/schemas/LogActivityResponse'
- '400':
- description: Bad request - invalid query parameter
- content:
- application/json:
+ $ref: '#/components/schemas/Error'
+ /deployments:
+ get:
+ tags:
+ - Projects
+ summary: List deployments across a user's projects
+ description: |
+ Lists Function and Frontend deployment attempts across every project the
+ user owns, newest first. Pass `project_id` to narrow the feed to a single
+ project.
+
+ Scope is project **ownership** (`projects.user_id`). `owner_id` names
+ whose deployments to return, not who started them — the actor is
+ `initiated_by_user_id`, which this endpoint does not filter on.
+
+ With a user token the scope is always the authenticated user: `owner_id`
+ may be omitted, or set to that same user, but naming anyone else is
+ refused with 403. Service callers on the management API must pass it,
+ since they have no authenticated user.
+
+ The owner is not checked for existence: an id with no projects returns an
+ empty page rather than `404`. Unlike `/users/{id}/usage`, this endpoint is
+ polled to detect an event, so a caller needs `404` to keep meaning "this
+ route is not served here" — which is how a consumer notices it is running
+ against an older release. A mistyped owner therefore reads as "nothing
+ deployed"; callers that need to tell those apart should verify the user
+ through `GET /users/{id}` first.
+
+ Ordering is selectable. The default is the feed order — most recent
+ attempt first. `completed_at.asc` orders by completion, oldest first, and
+ considers only attempts that finished; combined with `limit=1` and a
+ `status` filter it answers "when did this user first succeed" in one
+ bounded query.
+
+ Both pagination modes are supported, selected exactly as
+ `/projects/{id}/deployments` selects them: `cursor`/`ending_before` (or a
+ `limit` with no `page`) uses keyset pagination; otherwise `page`/`limit`
+ offset pagination. `page` with a cursor, and `cursor` with
+ `ending_before`, are rejected.
+
+ A cursor is bound to every filter *and* to `order`, so changing any of
+ them mid-pagination rejects the cursor rather than silently skipping or
+ repeating rows. The keyset position is `(created_at, id)` for
+ `created_at.desc` and `(completed_at, id)` for `completed_at.asc`.
+ operationId: listDeployments
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/DeploymentOwnerId'
+ - name: project_id
+ in: query
+ required: false
+ description: Restrict the feed to a single project owned by the user.
+ schema:
+ type: string
+ format: uuid
+ - name: created_after
+ in: query
+ required: false
+ description: Restrict results to attempts created at or after this timestamp.
+ schema:
+ type: string
+ format: date-time
+ - $ref: '#/components/parameters/DeploymentResourceType'
+ - $ref: '#/components/parameters/DeploymentStatus'
+ - $ref: '#/components/parameters/DeploymentOperation'
+ - $ref: '#/components/parameters/DeploymentOrder'
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PaginatedProjectDeployments'
+ '400':
+ description: Bad request - invalid filter or pagination
+ content:
+ application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
@@ -3286,53 +3175,66 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - project ownership required
+ description: Forbidden - owner_id names a different user
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project or resource not found
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/logs/search:
- post:
+ /projects/{id}/deployments:
+ get:
tags:
- - Logs
- summary: Search project logs
+ - Projects
+ summary: List deployments in a project
description: |
- Search or filter logs for one resource type in the project. Set
- `resource.type` to `function`, `frontend`, or `database`. Add
- `resource.ids` to filter to one or more resources, and add
- `resource.deployments.ids` to read deployment logs instead of runtime
- logs for functions and frontends. Deployment logs are not supported for
- databases. Database logs are a PRO-plan feature; requests for
- `resource.type=database` from a FREE-plan project owner return 403.
- Log history (runtime and deployment) is limited to the plan's retention
- window (FREE: 1 day, PRO: 30 days); older time ranges are clamped to that
- window.
- operationId: searchProjectLogs
+ Lists Function and Frontend deployment attempts across the project,
+ ordered most-recent first. Each item includes a normalized resource
+ reference so clients can render both resource types without extra
+ fetches.
+ operationId: listProjectDeployments
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/LogSearchRequest'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
+ - name: created_after
+ in: query
+ required: false
+ description: Restrict results to attempts created at or after this timestamp.
+ schema:
+ type: string
+ format: date-time
+ - name: resource_type
+ in: query
+ required: false
+ description: |
+ Restrict the feed to a single resource type. Omit to return both
+ Function and Frontend deployments.
+ schema:
+ type: string
+ enum:
+ - function
+ - frontend
responses:
'200':
description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/LogSearchResponse'
+ $ref: '#/components/schemas/PaginatedProjectDeployments'
'400':
- description: Bad request - invalid query parameter
+ description: Bad request - invalid identifier
content:
application/json:
schema:
@@ -3349,78 +3251,61 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project or resource not found
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/logs/stream:
- post:
+ /projects/{id}/deployments/summary:
+ get:
tags:
- - Logs
- summary: Stream project logs
+ - Projects
+ summary: Summarize deployments in a project
description: |
- Live-tail project logs as Server-Sent Events. The request body uses the
- resource selector plus `q`, `start_time`, and `limit`,
- including runtime logs and function/frontend deployment logs selected
- with `resource.deployments`. Deployment logs are not supported for
- databases. Database logs are a PRO-plan feature; `resource.type=database`
- from a FREE-plan project owner returns 403. The `q` field uses the same
- syntax as search and activity requests. Do not send `cursor` or
- `end_time`; use `/logs/search` for range backfills.
- Explicit historical `start_time` values are limited to the plan's
- retention window (FREE: 1 day, PRO: 30 days). Resume with
- `Last-Event-ID` or the `last_event_id` query parameter. The cursor is
- bound to the request body: the resource selector and every filter must
- match the original request when reconnecting, otherwise the request is
- rejected with `400`.
-
- This is a live tail, not a gap-free backfill. On connect or reconnect the
- server delivers at most `limit` of the most recent matching events from
- the cursor position and then follows new events; events older than that
- window are not replayed. Use `/logs/search` to backfill a time range.
- operationId: streamProjectLogs
+ Summarizes deployment attempts for one comparable resource pipeline.
+ Success rate uses conclusive outcomes only: active and deleted attempts
+ are successful; failed and degraded attempts are failures; in-progress
+ and superseded attempts are excluded. Median build duration includes
+ completed, non-superseded attempts with recorded build work,
+ including failed builds.
+ operationId: summarizeProjectDeployments
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: Last-Event-ID
- in: header
+ - name: search
+ in: query
required: false
+ description: Restrict the summary to resource names containing this value.
schema:
type: string
- description: Opaque stream cursor from the most recent SSE `id` field.
- - name: last_event_id
+ - name: resource_type
+ in: query
+ required: true
+ description: Restrict the summary to one comparable deployment pipeline.
+ schema:
+ type: string
+ enum:
+ - function
+ - frontend
+ - name: created_after
in: query
required: false
+ description: Restrict results to attempts created at or after this timestamp.
schema:
type: string
- description: Opaque stream cursor fallback when setting `Last-Event-ID` is not practical.
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/LogStreamRequest'
+ format: date-time
responses:
'200':
- description: Server-Sent Events stream. `log` events contain a JSON `LogSearchEvent`; `warning` events contain a JSON object with an `error` field.
+ description: Successful response
content:
- text/event-stream:
+ application/json:
schema:
- type: string
- examples:
- log:
- summary: Log event
- value: |
- : connected
-
- id: STREAM_CURSOR
- event: log
- data: {"id":"LOG_EVENT_ID","timestamp":"2024-01-01T12:00:00Z","level":"info","message":"User logged in","resource":{"type":"function","id":"550e8400-e29b-41d4-a716-446655440000","name":"login"},"region":"us-east-1"}
+ $ref: '#/components/schemas/ProjectDeploymentSummary'
'400':
- description: Bad request - invalid selector, stream cursor, or unsupported stream field
+ description: Bad request - invalid identifier or filter
content:
application/json:
schema:
@@ -3437,114 +3322,80 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project or resource not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
'500':
- description: Internal server error - log streaming setup failed
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/functions/batch:
- post:
+ /projects/{id}/domains:
+ get:
tags:
- - Functions
- summary: Deploy multiple functions in one request
+ - Frontends
+ summary: List all custom domains in a project
description: |
- Upload multiple function source archives in one multipart request. Each archive should contain source files
- plus dependency manifests/lockfiles, not installed dependency directories. ZIP and tar.gz uploads are
- accepted and normalized to tar.gz before storage. The API enforces `SOURCE_ARCHIVE_SIZE_LIMIT_MB`
- for each uploaded and normalized source archive. The server records a shared
- deployment batch ID for the resulting function deployments. Each function deployment runs its own
- compile/publish workflow concurrently, and each publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB`
- for the final container image.
- One batch request can include up to 100 functions. Submit multiple batch requests for larger projects.
- If one function fails before its workflow starts, already-started function deployments are left
- running and the failed function is reported in the `failed` array. Failed new functions are deleted;
- failed updates are rolled back to their previous metadata/status where possible.
- operationId: createFunctionsBatch
+ Project-scoped custom-domain list. Returns every active custom
+ domain across every frontend in the project (excludes soft-deleted
+ rows). Each item inlines the linked frontend's id and name so the
+ UI does not need a second fetch to render the "Linked to" column.
+ operationId: listProjectCustomDomains
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- properties:
- functions:
- type: string
- description: |
- JSON array of functions with `name`, `runtime`, optional `handler`, and `file_field`. Each `file_field` must name a multipart file field containing that function's ZIP or tar.gz source bundle.
-
- Each entry may also declare `variable_scope` (`all` or `scoped`) and `variables` (an array of project variable names). Omitting them leaves the function's stored declaration unchanged. Volcano detects direct environment references in the uploaded source code and keeps them separate from the declared names: detected names are not written back to the declaration and do not appear in a config export. A scoped function receives its declared names plus the detected ones the project defines; a detected name the project does not define is ignored, since such a reference is often optional. Detection reads code only, so a name appearing solely in a comment or in an unrelated string is not a reference. Declare a name when the function reads it through a computed key, or when it must not deploy without the variable. The request is rejected with 400 before anything is deployed if a scoped function declares a variable the project does not define, or if the resulting environment exceeds 4096 bytes.
- code_0:
- type: string
- format: binary
- description: Function ZIP or tar.gz archive referenced by the first manifest entry's `file_field`; additional code_N file fields may be included. Each archive is subject to SOURCE_ARCHIVE_SIZE_LIMIT_MB.
- required:
- - functions
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
responses:
- '202':
- description: Batch deployment accepted
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/BatchFunctionDeployResponse'
- '207':
- description: Batch deployment partially accepted; successful functions started deployment and failed functions were compensated where possible
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/BatchFunctionDeployResponse'
+ $ref: '#/components/schemas/PaginatedProjectCustomDomains'
'400':
- description: Bad request
+ description: Bad request - invalid identifier
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Function limit exceeded for the project
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: |
- A name in the batch is held by a function of the other kind — a
- function cannot change kind — or the project's source is managed by
- Git, where deploys come from a push to the production branch.
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: |
- The batch carries a durable function and durable deploys are paused
- platform-wide. The same request succeeds once they are re-enabled.
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/schedulers:
+ /projects/{id}/functions:
get:
tags:
- Functions
- summary: List every function scheduler in a project
+ summary: List all functions in a project
description: |
- Project-scoped counterpart to `/projects/{id}/functions/{functionId}/schedulers`.
- Returns schedulers across all functions in the project, ordered by
- creation time descending, with standard page/limit pagination so
- clients don't have to fan out one request per function.
- operationId: listProjectSchedulers
+ Supports two mutually exclusive pagination modes. Offset mode uses `page`
+ and `limit` and returns `next` (URL). Cursor mode uses `cursor` and
+ `limit`, supports `search` (case-insensitive name match), and returns
+ `next_cursor`/`prev_cursor`. Sending both `page` and `cursor` (or `page`
+ and `search`) returns 400.
+ operationId: listFunctions
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/Page'
@@ -3555,31 +3406,13 @@ paths:
- $ref: '#/components/parameters/Search'
responses:
'200':
- description: Project schedulers
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/FunctionSchedulerListResponse'
- /projects/{id}/functions/{functionId}/schedulers:
- get:
- tags:
- - Functions
- summary: List schedulers for a function
- operationId: listFunctionSchedulers
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FunctionId'
- responses:
- '200':
- description: Function schedulers
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionSchedulerListResponse'
+ $ref: '#/components/schemas/PaginatedFunctions'
'404':
- description: Function not found
+ description: Project not found
content:
application/json:
schema:
@@ -3587,71 +3420,189 @@ paths:
post:
tags:
- Functions
- summary: Create a scheduler for a function
- description: Creates regional scheduled invocation jobs. Requested regions must be a subset of the function's deployed regions.
- operationId: createFunctionScheduler
+ summary: Create or update function code
+ description: |
+ Upload a serverless function source bundle. Direct API clients may send the function code
+ as a ZIP or tar.gz archive via multipart/form-data. The API stores a normalized tar.gz
+ source archive.
+ Cloud deploys should include source files and dependency manifests/lockfiles, not installed
+ dependency directories. Volcano installs Node.js, Python, and Ruby dependencies during the
+ function compile build.
+ Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
+ does not apply its own source archive size limit. After the final container image is
+ built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
+ Uploaded source archives cannot contain symlink entries. Safe symlinks created during
+ the cloud build are materialized before publish.
+ Volcano builds and deploys the function asynchronously after upload. A deployment that starts
+ immediately returns a Function resource with `status: provisioning`, then transitions to
+ `active` or `failed`. If another deployment is running, the response preserves the resource's
+ current status and exposes the queued deployment through `pending_deployment_id`.
+ Existing function traffic continues to use the last known-good runtime during an update. A failed
+ update keeps that runtime available and records the attempted deployment as failed.
+ Only one deployment runs for a given function. A newer request supersedes any queued request
+ and starts after the running deployment. Different functions and projects deploy concurrently.
+ If a function with the same name already exists in the project, this operation updates that
+ function's runtime, handler, and source bundle and returns `200 OK`.
+ Each project can contain up to 10,000 functions. Creating a new function over this cap returns 403.
+ operationId: createFunction
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FunctionId'
requestBody:
required: true
content:
- application/json:
+ multipart/form-data:
schema:
- $ref: '#/components/schemas/CreateFunctionSchedulerRequest'
+ type: object
+ required:
+ - name
+ - code
+ - runtime
+ properties:
+ name:
+ type: string
+ description: DNS-safe function name (lowercase letters, numbers, hyphens; cannot start or end with hyphen)
+ maxLength: 63
+ pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
+ example: my-api-function
+ code:
+ type: string
+ format: binary
+ description: ZIP or tar.gz archive containing function source code plus dependency manifests/lockfiles. The API enforces SOURCE_ARCHIVE_SIZE_LIMIT_MB and stores a normalized tar.gz source archive.
+ runtime:
+ type: string
+ enum:
+ - nodejs22.x
+ - nodejs24.x
+ - python3.10
+ - python3.11
+ - python3.12
+ - python3.13
+ - python3.14
+ - ruby3.3
+ - ruby3.4
+ - ruby4.0
+ description: |
+ Runtime environment. Required.
+ - Node.js: nodejs22.x, nodejs24.x
+ - Python: python3.10, python3.11, python3.12, python3.13, python3.14
+ - Ruby: ruby3.3, ruby3.4, ruby4.0
+ example: nodejs24.x
+ handler:
+ type: string
+ description: |
+ The name of the function to invoke. Defaults to "handler" if not specified.
+ Your code must export/define a function with this name:
+ - Node.js: exports.handler (in index.js)
+ - Python: def handler() (in main.py)
+ - Ruby: def handler() (in main.rb)
+ default: handler
+ example: handler
+ is_public:
+ type: boolean
+ description: |
+ Whether the function can be reached through public invocation
+ ingress. Omit it to keep the function's current visibility; a
+ new function starts private.
+ invocation_mode:
+ $ref: '#/components/schemas/FunctionInvocationMode'
+ http_auth_mode:
+ $ref: '#/components/schemas/FunctionHTTPAuthMode'
+ openapi_spec:
+ type: string
+ description: JSON-encoded OpenAPI 3.0 or 3.1 metadata for an HTTP-mode function.
+ variable_scope:
+ type: string
+ enum:
+ - all
+ - scoped
+ x-enum-varnames:
+ - CreateFunctionMultipartBodyVariableScopeAll
+ - CreateFunctionMultipartBodyVariableScopeScoped
+ description: |
+ Which project variables this function receives. `all` (the default) gives it only project variables marked `shared: true`; `scoped` gives it only the variables it selects. Omitting this leaves an existing function's scope unchanged.
+ variables:
+ type: string
+ description: |
+ JSON-encoded array of project variable names this function requires, on top of the ones detected in its source. A declared name the project does not define is rejected with 400; a detected name it does not define is ignored. Only used when `variable_scope` is `scoped`. Omitting this leaves an existing function's declared names unchanged.
responses:
+ '200':
+ description: Existing function updated; its deployment was started or queued
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Function'
'201':
- description: Scheduler created
+ description: Function created and deployment workflow started
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionScheduler'
+ $ref: '#/components/schemas/Function'
'400':
- description: |
- Invalid schedule, geofenced region, a scheduler of this name
- already exists on the function, or the function is not active.
+ description: Bad request (invalid file, too large, etc.)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
+ description: Function limit exceeded for the project
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
description: |
- Schedulers are not available on this plan, or the project already
- holds as many as the plan allows. The cap counts standard and
- durable function schedulers together.
+ Function deletion is queued or running, or the name is already held
+ by a durable function — a function cannot change kind.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Function not found
+ '429':
+ description: |
+ The owner's billing-cycle build-minutes allowance is spent. The
+ error names the allowance and carries a link to the usage page.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/functions/{functionId}/schedulers/{schedulerId}:
+ '500':
+ description: Internal server error (function deployment failed)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: |
+ Build usage could not be read, so the allowance could not be
+ checked. The same request succeeds once it can be.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/functions/{functionId}:
get:
tags:
- Functions
- summary: Get a function scheduler
- operationId: getFunctionScheduler
+ summary: Get function by ID
+ operationId: getFunction
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/FunctionId'
- - $ref: '#/components/parameters/SchedulerId'
responses:
'200':
- description: Function scheduler
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionScheduler'
+ $ref: '#/components/schemas/Function'
'404':
- description: Function or scheduler not found
+ description: Function not found
content:
application/json:
schema:
@@ -3659,37 +3610,50 @@ paths:
patch:
tags:
- Functions
- summary: Update a function scheduler
- operationId: updateFunctionScheduler
+ summary: Update function settings
+ operationId: updateFunction
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/FunctionId'
- - $ref: '#/components/parameters/SchedulerId'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateFunctionSchedulerRequest'
+ $ref: '#/components/schemas/UpdateFunctionRequest'
+ examples:
+ makePublic:
+ summary: Make function public (anon keys can invoke)
+ value:
+ is_public: true
+ makePrivate:
+ summary: Make function private (default behavior)
+ value:
+ is_public: false
responses:
'200':
- description: Scheduler updated
+ description: Function updated
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionScheduler'
+ $ref: '#/components/schemas/Function'
'400':
- description: |
- Invalid schedule, geofenced region, or a scheduler of this name
- already exists on the function.
+ description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Function or scheduler not found
+ description: Function not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: Function settings conflict with attached Frontend Function routes
content:
application/json:
schema:
@@ -3697,1241 +3661,1635 @@ paths:
delete:
tags:
- Functions
- summary: Delete a function scheduler
- operationId: deleteFunctionScheduler
+ summary: Delete a function
+ description: |
+ Schedules asynchronous function deletion. If another deployment is running, the function
+ preserves its current status and exposes the queued deletion through `pending_deployment_id`.
+ Its status changes to `deleting` when cleanup starts. After cleanup, it returns 404 and no
+ longer appears in function lists.
+ operationId: deleteFunction
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/FunctionId'
- - $ref: '#/components/parameters/SchedulerId'
responses:
- '204':
- description: Scheduler deleted
+ '202':
+ description: Function deletion started or queued
'404':
- description: Function or scheduler not found
+ description: Function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/functions/{functionId}/deployments:
- get:
+ /functions/{functionId}/invoke:
+ post:
tags:
- Functions
- summary: List function deployments
- operationId: listFunctionDeployments
+ summary: Invoke a function
+ description: |
+ Invoke a serverless function.
+
+ **With Service Key** (admin/background operations):
+ - Use for background jobs, webhooks, cron, admin operations
+ - Function receives payload only (no user context)
+ - Database queries bypass RLS (admin access)
+
+ **With Auth User Token** (user-facing):
+ - Use for user-initiated actions
+ - Function receives payload + `__volcano_auth` context:
+ ```javascript
+ {
+ user_id: "uuid",
+ email: "user@example.com",
+ project_id: "uuid",
+ role: "authenticated" or "anonymous"
+ }
+ ```
+ - Database queries enforce RLS (user-scoped data)
+
+ **With Anon Key** (public function only):
+ - Requires anon key permission: `functions.invoke`
+ - Function must have `is_public: true`
+ - Function receives payload only (no `__volcano_auth`)
+
+ **Transport and CORS:**
+ - This operation is the authenticated direct RPC endpoint and always uses the
+ POST `{payload: ...}` contract, including for functions whose DNS ingress is
+ configured in HTTP mode.
+ - The geo-routed DNS ingress is the function's `invoke_url`. It is on a
+ different domain from this API, so it cannot be derived from the API host.
+ - RPC-mode DNS ingress accepts POST at `/`. HTTP-mode DNS ingress accepts GET,
+ HEAD, POST, PUT, PATCH, and DELETE at `/` and nested paths.
+ - Direct and RPC-mode CORS preflight advertises `POST, OPTIONS`. HTTP-mode DNS
+ preflight advertises `GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS`.
+ - `http_auth_mode: none` applies only to public HTTP-mode DNS ingress; this
+ direct operation always requires a Volcano credential.
+
+ **Durable functions are not invocable here.** A durable function's id
+ answers 404, whatever its visibility, because a synchronous call would
+ run it with no execution record, no idempotency and no concurrency
+ accounting. Start one with
+ `POST /durable-functions/{functionId}/executions`.
+ operationId: invokeFunction
security:
- - UserToken: []
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/FunctionId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FunctionInvocationRequest'
+ examples:
+ serviceCall:
+ summary: Admin/background operation (service key)
+ value:
+ payload:
+ action: process_batch
+ items:
+ - 1
+ - 2
+ - 3
+ userCall:
+ summary: User-initiated call (auth token)
+ value:
+ payload:
+ action: get_profile
+ anonCall:
+ summary: Public function call (anon key)
+ value:
+ payload:
+ action: ping_public_endpoint
responses:
'200':
- description: Successful response
+ description: Function response (passthrough from function runtime)
+ headers:
+ X-Volcano-Version:
+ description: Volcano API/runtime version that served this invocation (`` in production, `-` in non-production)
+ schema:
+ type: string
+ X-Volcano-Region:
+ description: Region the function ran in (for example `us-east-1`)
+ schema:
+ type: string
+ X-Volcano-Proxy-Ms:
+ description: Milliseconds Volcano spent preparing the invocation, counted from the request arriving until the function was dispatched. Present only when the function was invoked. Does not include function execution.
+ schema:
+ type: integer
+ minimum: 0
+ X-Volcano-Proxy-Handler-Ms:
+ description: The part of `X-Volcano-Proxy-Ms` spent in the invoke endpoint itself. Subtract it from `X-Volcano-Proxy-Ms` to see what authentication and request validation cost. Present only when the function was invoked.
+ schema:
+ type: integer
+ minimum: 0
+ X-Volcano-Compute-Ms:
+ description: Milliseconds spent running the function, from dispatch until it returned. Present only when the function was invoked. Does not include proxy preparation.
+ schema:
+ type: integer
+ minimum: 0
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedFunctionDeployments'
- '404':
- description: Function not found
+ $ref: '#/components/schemas/FunctionInvocationResponse'
+ '400':
+ description: Bad request - invalid payload or function in failed state
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions:
- get:
- tags:
- - Durable Functions
- summary: List all durable functions in a project
- description: |
- Standard functions never appear here, and durable functions never appear
- under `/projects/{id}/functions`. The two are separate collections.
- operationId: listDurableFunctions
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Search'
- responses:
- '200':
- description: Successful response
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedDurableFunctions'
- '400':
- description: Bad request - invalid pagination parameters
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - CORS blocked, missing `functions.invoke`, or private function with anon key
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project not found
+ description: Function not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: |
+ Rate limit exceeded (per-function or project-wide limit), or the
+ owning platform user's billing-cycle bandwidth allowance (aggregate ingress +
+ egress) was exceeded.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: |
+ Function still provisioning or rate limiting service unavailable.
+ A freshly deployed (or updated) function may briefly report
+ `provisioning` and reject invocations until the background status
+ reconciler observes its deployment workflow completing and transitions
+ it to `active`. This is expected for a few seconds after deploy; clients
+ should retry.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ default:
+ description: Function response (passthrough; status code/body/headers come from the function)
+ headers:
+ X-Volcano-Version:
+ description: Volcano API/runtime version that served this invocation (`` in production, `-` in non-production)
+ schema:
+ type: string
+ X-Volcano-Region:
+ description: Region the function ran in (for example `us-east-1`)
+ schema:
+ type: string
+ X-Volcano-Proxy-Ms:
+ description: Milliseconds Volcano spent preparing the invocation, counted from the request arriving until the function was dispatched. Present only when the function was invoked. Does not include function execution.
+ schema:
+ type: integer
+ minimum: 0
+ X-Volcano-Proxy-Handler-Ms:
+ description: The part of `X-Volcano-Proxy-Ms` spent in the invoke endpoint itself. Subtract it from `X-Volcano-Proxy-Ms` to see what authentication and request validation cost. Present only when the function was invoked.
+ schema:
+ type: integer
+ minimum: 0
+ X-Volcano-Compute-Ms:
+ description: Milliseconds spent running the function, from dispatch until it returned. Present only when the function was invoked. Does not include proxy preparation.
+ schema:
+ type: integer
+ minimum: 0
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FunctionInvocationResponse'
+ /durable-functions/{functionId}/executions:
post:
tags:
- Durable Functions
- summary: Create or update a durable function
+ summary: Start a durable execution from an application
description: |
- Upload a durable function source bundle. Creates the function on the
- first call for a name and redeploys it on every call after that, the
- same create-or-update contract `POST /projects/{id}/functions` has.
+ Starts an execution of a durable function using an application
+ credential, and returns its handle.
- Volcano builds and deploys asynchronously. A deployment that starts
- immediately returns `status: provisioning`, then transitions to `active`
- or `failed`; a deployment that has to wait for a running one is exposed
- through `pending_deployment_id`. Existing executions keep running
- against the runtime they started on.
+ This is the durable counterpart of `POST /functions/{functionId}/invoke`,
+ and it is the endpoint an application calls. Like that one, it is not
+ project-scoped: an anon key, a service key and an auth user token each
+ carry their own project. The project-scoped collection under
+ `/projects/{id}/durable-functions/...` remains the owner's management
+ surface.
- The `durable` configuration is derived from the project's plan rather
- than supplied here, and is fixed once the function exists. A name
- already held by a standard function is rejected with 409: a function
- cannot change kind.
- operationId: createDurableFunction
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- required:
- - name
- - code
- - runtime
- properties:
- name:
- type: string
- description: DNS-safe function name (lowercase letters, numbers, hyphens; cannot start or end with hyphen)
- maxLength: 63
- pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
- example: order-pipeline
- code:
- type: string
- format: binary
- description: ZIP or tar.gz archive containing function source code plus dependency manifests/lockfiles.
- runtime:
- type: string
- enum:
- - nodejs22.x
- - nodejs24.x
- - python3.13
- - python3.14
- description: |
- Runtime environment. Required. Durable execution needs the
- durable authoring API, which ships for these runtimes only;
- any other runtime is rejected with 400 and the response
- names the ones that work. Note that a durable Python
- function needs a newer runtime than a standard one defaults
- to. `GET /functions/runtimes` reports `durable_capable` per
- runtime.
- example: nodejs24.x
- handler:
- type: string
- description: The name of the function to invoke. Defaults to "handler" if not specified.
- default: handler
- example: handler
- is_public:
- type: boolean
- description: |
- Whether anon keys with `functions.invoke` may start an
- execution. Redeploying is the only way to change it, since
- the collection has no update endpoint; omit it to keep the
- current visibility, and a new function starts private.
+ **With a service key or an auth user token:** any durable function in
+ the project.
- The standard collection's synchronous invocation fields —
- `invocation_mode`, `http_auth_mode`, `openapi_spec` —
- configure a request path no durable route serves, and are
- rejected with 400 rather than ignored.
- variable_scope:
- type: string
- enum:
- - all
- - scoped
- description: |
- Which project variables this function receives. `all` (the default) gives it every project variable; `scoped` gives it only the variables it selects. Omitting this leaves an existing function's scope unchanged.
- variables:
- type: string
- description: |
- JSON-encoded array of project variable names this function requires, on top of the ones detected in its source. A declared name the project does not define is rejected with 400; a detected name it does not define is ignored. Only used when `variable_scope` is `scoped`. Omitting this leaves an existing function's declared names unchanged.
+ **With an anon key:** requires the `functions.invoke` permission, and
+ the function must have `is_public: true`.
+
+ Starting is all this endpoint does. Reading a result or stopping an
+ execution requires the project owner's token, because an anon key is
+ shared by everyone who loads the page and an execution is addressed by
+ id alone.
+
+ Send `X-Volcano-Execution-Name` to make the start idempotent: repeating
+ a start with the same name returns the existing execution instead of
+ beginning a second one.
+
+ Each execution counts once against the project's durable execution
+ allowance, however many times the start is retried under the same
+ execution name, and the number in flight at once is capped by the plan.
+ The operations the execution performs are counted against the durable
+ operations allowance when it finishes.
+ operationId: startDurableExecutionFromApplication
+ security:
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - name: X-Volcano-Execution-Name
+ in: header
+ required: false
+ description: |
+ Idempotency key for this execution. Generated when omitted. A repeat
+ under a name that already names a running execution returns that
+ execution and is not charged again.
+
+ Letters, digits, `-`, `_` and `.`, up to 255 characters. Anything
+ else is rejected with `400`.
+ schema:
+ type: string
+ maxLength: 255
+ pattern: ^[A-Za-z0-9._-]+$
+ requestBody:
+ required: false
+ content:
+ application/json:
+ schema:
+ description: Input passed to the function, up to 256 KiB.
responses:
- '200':
- description: Existing durable function updated; its deployment was started or queued
+ '202':
+ description: Execution accepted and started
content:
application/json:
schema:
- $ref: '#/components/schemas/DurableFunction'
- '201':
- description: Durable function created and deployment workflow started
+ $ref: '#/components/schemas/DurableExecution'
+ '400':
+ description: Payload is not valid JSON, or the execution name is invalid
content:
application/json:
schema:
- $ref: '#/components/schemas/DurableFunction'
- '400':
+ $ref: '#/components/schemas/Error'
+ '401':
description: |
- Bad request (invalid archive, unsupported runtime, invalid name, or a
- project region that does not offer durable execution — a durable
- function deploys to every region of its project)
+ Missing or invalid credential. Also returned for a platform user
+ token, which is not an application credential; project owners start
+ executions through the project-scoped collection.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Durable function limit exceeded for the project
+ description: |
+ The anon key lacks `functions.invoke`, the function is not public,
+ or the request's origin is refused by the project's CORS policy.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: |
+ Durable function not found. Also returned for a standard function's
+ id and for a durable function in another project, so the response
+ cannot be used to tell those apart.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: Name is held by a standard function, or a deletion is queued or running
+ description: |
+ Function is not deployed yet, or has no deployed region. Also
+ returned when two starts under the same execution name raced and
+ both released it, which is retryable as it stands.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error (function deployment failed)
+ '413':
+ description: Payload is larger than 256 KiB
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: |
+ The project has too many executions in flight for its plan, the
+ function invocation rate limit was exceeded, the project is over
+ its bandwidth cap, or the account is out of one of its
+ billing-cycle durable allowances: executions, operations, or
+ compute. Operations and compute are counted once an execution
+ finishes, so a refusal on either never interrupts an execution
+ already running — it declines the next start.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: |
- Durable deploys are paused platform-wide. The same request succeeds
- once they are re-enabled; executions already running are unaffected.
+ Durable execution is not available in this environment, or the
+ plan terms for the start could not be read. The first means the
+ capability is paused or this deployment cannot serve it, so it is
+ not one to retry in a loop; the second is transient.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}:
+ /functions/resolve:
get:
tags:
- - Durable Functions
- summary: Get durable function by ID or name
- operationId: getDurableFunction
+ - Functions
+ summary: Resolve function name for invocation
+ description: |
+ Resolves a DNS-safe function name to its function ID and invocation URL within the caller's project.
+
+ SDKs use this endpoint internally to invoke by function name while routing by function ID.
+ Invoke the returned `invoke_url` as-is. It does not share a domain with the API, so a host
+ built from the API URL will not reach the function. When the deployment serves no public
+ invocation domain, as in local development, `invoke_url` is omitted and callers invoke
+ through `POST /functions/{functionId}/invoke`.
+
+ **With Service Key**:
+ - Allowed
+
+ **With Auth User Token**:
+ - Allowed
+
+ **With Anon Key**:
+ - Requires anon key permission: `functions.invoke`
+ - Function must have `is_public: true`
+ operationId: resolveFunctionForInvocation
security:
- - UserToken: []
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
+ - name: name
+ in: query
+ required: true
+ schema:
+ type: string
+ maxLength: 63
+ pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
+ description: DNS-safe function name (lowercase letters, numbers, hyphens; cannot start or end with hyphen)
+ example: my-function
responses:
'200':
- description: Successful response
+ description: Function resolved successfully
content:
application/json:
schema:
- $ref: '#/components/schemas/DurableFunction'
- '404':
- description: Durable function not found
+ $ref: '#/components/schemas/ResolveFunctionResponse'
+ '400':
+ description: Bad request - missing or invalid function name
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
- tags:
- - Durable Functions
- summary: Delete a durable function
- description: |
- Accepted for asynchronous teardown; the work continues after the
- response. The function's executions go with it: history stops being
- readable whatever `retention_days` had left, and the executions still
- running stop counting against the project's concurrency cap. Stop an
- execution first if you need it to end before the function does.
- operationId: deleteDurableFunction
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- responses:
- '202':
- description: Deletion accepted and teardown started
- '404':
- description: |
- Durable function not found. Also returned for an id that names a
- durable function in another project, so the response cannot be used
- to tell the two apart.
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}/deployments:
- get:
- tags:
- - Durable Functions
- summary: List durable function deployments
- operationId: listDurableFunctionDeployments
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- responses:
- '200':
- description: Successful response
+ '403':
+ description: Forbidden - CORS blocked or missing `functions.invoke` permission for anon key
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedFunctionDeployments'
+ $ref: '#/components/schemas/Error'
'404':
- description: Durable function not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}/schedulers:
- get:
- tags:
- - Durable Functions
- summary: List schedulers for a durable function
- description: |
- The durable collection's counterpart to
- `/projects/{id}/functions/{functionId}/schedulers`. A standard
- function's id is not accepted here, and a durable function's id is not
- accepted there.
- operationId: listDurableFunctionSchedulers
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- responses:
- '200':
- description: Durable function schedulers
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/FunctionSchedulerListResponse'
- '404':
- description: Durable function not found
+ description: |
+ Function not found (or private function with anon key). A durable
+ function is never resolvable here: it is started through
+ `POST /durable-functions/{functionId}/executions`, not invoked.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ /projects/{id}/logs/activity:
post:
tags:
- - Durable Functions
- summary: Create a scheduler for a durable function
+ - Logs
+ summary: Get project log activity
description: |
- Each tick starts an execution rather than invoking the function, under
- an execution name derived from the run, so a retried tick resolves to
- the execution it already started. Requested regions must be a subset of
- the function's deployed regions.
-
- A tick draws on the same durable allowances and concurrency cap a
- manual start does, and a tick that would exceed the cap fails that run.
- operationId: createDurableFunctionScheduler
+ Retrieve bucketed log counts for one resource type in the project. Set
+ `resource.type` to `function`, `frontend`, or `database`. Add
+ `resource.ids` to filter to one or more resources, and add
+ `resource.deployments.ids` to count deployment logs instead of runtime
+ logs for functions and frontends. Deployment logs are not supported for
+ databases. Database logs are a PRO-plan feature; `resource.type=database`
+ from a FREE-plan project owner returns 403. The activity window is limited
+ to the plan's retention window (FREE: 1 day, PRO: 30 days); older start
+ times are clamped to that window.
+ operationId: getProjectLogActivity
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateFunctionSchedulerRequest'
+ $ref: '#/components/schemas/LogActivityRequest'
responses:
- '201':
- description: Scheduler created
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionScheduler'
+ $ref: '#/components/schemas/LogActivityResponse'
'400':
- description: |
- Invalid schedule, geofenced region, a scheduler of this name
- already exists on the function, or the function is not active.
+ description: Bad request - invalid query parameter
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: |
- Schedulers are not available on this plan, or the project already
- holds as many as the plan allows. The cap counts standard and
- durable function schedulers together.
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Durable function not found
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}/schedulers/{schedulerId}:
- get:
- tags:
- - Durable Functions
- summary: Get a durable function scheduler
- operationId: getDurableFunctionScheduler
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/SchedulerId'
- responses:
- '200':
- description: Durable function scheduler
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/FunctionScheduler'
'404':
- description: Durable function or scheduler not found
+ description: Project or resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- patch:
+ /projects/{id}/logs/search:
+ post:
tags:
- - Durable Functions
- summary: Update a durable function scheduler
- operationId: updateDurableFunctionScheduler
+ - Logs
+ summary: Search project logs
+ description: |
+ Search or filter logs for one resource type in the project. Set
+ `resource.type` to `function`, `frontend`, or `database`. Add
+ `resource.ids` to filter to one or more resources, and add
+ `resource.deployments.ids` to read deployment logs instead of runtime
+ logs for functions and frontends. Deployment logs are not supported for
+ databases. Database logs are a PRO-plan feature; requests for
+ `resource.type=database` from a FREE-plan project owner return 403.
+ Log history (runtime and deployment) is limited to the plan's retention
+ window (FREE: 1 day, PRO: 30 days); older time ranges are clamped to that
+ window.
+ operationId: searchProjectLogs
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/SchedulerId'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateFunctionSchedulerRequest'
+ $ref: '#/components/schemas/LogSearchRequest'
responses:
'200':
- description: Scheduler updated
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionScheduler'
+ $ref: '#/components/schemas/LogSearchResponse'
'400':
- description: |
- Invalid schedule, geofenced region, or a scheduler of this name
- already exists on the function.
+ description: Bad request - invalid query parameter
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Durable function or scheduler not found
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
- tags:
- - Durable Functions
- summary: Delete a durable function scheduler
- operationId: deleteDurableFunctionScheduler
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/SchedulerId'
- responses:
- '204':
- description: Scheduler deleted
'404':
- description: Durable function or scheduler not found
+ description: Project or resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}/executions:
+ /projects/{id}/logs/stream:
post:
tags:
- - Durable Functions
- summary: Start a durable execution
+ - Logs
+ summary: Stream project logs
description: |
- Starts an execution and returns its handle. Never returns a result: an
- execution can outlive any request a client could hold open, so the
- result is read back from
- `GET /projects/{id}/durable-functions/{functionId}/executions/{executionId}`.
-
- The request body is the execution's input and must be valid JSON if
- present. An empty body starts the execution with no input.
-
- Send `X-Volcano-Execution-Name` to make the start idempotent: repeating a
- start with the same name returns the existing execution instead of
- beginning a second one.
+ Live-tail project logs as Server-Sent Events. The request body uses the
+ resource selector plus `q`, `start_time`, and `limit`,
+ including runtime logs and function/frontend deployment logs selected
+ with `resource.deployments`. Deployment logs are not supported for
+ databases. Database logs are a PRO-plan feature; `resource.type=database`
+ from a FREE-plan project owner returns 403. The `q` field uses the same
+ syntax as search and activity requests. Do not send `cursor` or
+ `end_time`; use `/logs/search` for range backfills.
+ Explicit historical `start_time` values are limited to the plan's
+ retention window (FREE: 1 day, PRO: 30 days). Resume with
+ `Last-Event-ID` or the `last_event_id` query parameter. The cursor is
+ bound to the request body: the resource selector and every filter must
+ match the original request when reconnecting, otherwise the request is
+ rejected with `400`.
- Each execution counts against the project's durable execution
- allowance, the operations it performs count against the durable
- operations allowance when it finishes, and the number of executions in
- flight at once is capped by the plan.
- operationId: startDurableExecution
+ This is a live tail, not a gap-free backfill. On connect or reconnect the
+ server delivers at most `limit` of the most recent matching events from
+ the cursor position and then follows new events; events older than that
+ window are not replayed. Use `/logs/search` to backfill a time range.
+ operationId: streamProjectLogs
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - name: X-Volcano-Execution-Name
+ - name: Last-Event-ID
in: header
required: false
- description: |
- Idempotency key for this execution. Generated when omitted. A repeat
- under a name that already names a running execution returns that
- execution and is not charged again.
-
- Letters, digits, `-`, `_` and `.`, up to 255 characters. Anything
- else is rejected with `400`.
schema:
type: string
- maxLength: 255
- pattern: ^[A-Za-z0-9._-]+$
+ description: Opaque stream cursor from the most recent SSE `id` field.
+ - name: last_event_id
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Opaque stream cursor fallback when setting `Last-Event-ID` is not practical.
requestBody:
- required: false
+ required: true
content:
application/json:
schema:
- description: Input passed to the function, up to 256 KiB.
+ $ref: '#/components/schemas/LogStreamRequest'
responses:
- '202':
- description: Execution accepted and started
+ '200':
+ description: Server-Sent Events stream. `log` events contain a JSON `LogSearchEvent`; `warning` events contain a JSON object with an `error` field.
content:
- application/json:
+ text/event-stream:
schema:
- $ref: '#/components/schemas/DurableExecution'
+ type: string
+ examples:
+ log:
+ summary: Log event
+ value: |
+ : connected
+
+ id: STREAM_CURSOR
+ event: log
+ data: {"id":"LOG_EVENT_ID","timestamp":"2024-01-01T12:00:00Z","level":"info","message":"User logged in","resource":{"type":"function","id":"550e8400-e29b-41d4-a716-446655440000","name":"login"},"region":"us-east-1"}
'400':
- description: Payload is not valid JSON, or the execution name is invalid
+ description: Bad request - invalid selector, stream cursor, or unsupported stream field
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Durable function not found
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: |
- Function is not deployed yet, or has no deployed region. Also
- returned when two starts under the same execution name raced and
- both released it, which is retryable as it stands.
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '413':
- description: Payload exceeds the maximum execution input size
+ '404':
+ description: Project or resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- description: |
- Too many executions already in flight for this project, or the
- account is out of one of its billing-cycle durable allowances:
- executions, operations, or compute. An owner-started execution is
- metered exactly like an application-started one.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: |
- Durable execution is not available in this environment, or the
- usage limit service could not be reached to charge the start. The
- first is returned by a deployment that has no durable execution
- engine, such as a local one, and is not retryable there; the second
- is transient.
+ '500':
+ description: Internal server error - log streaming setup failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- get:
+ /projects/{id}/functions/batch:
+ post:
tags:
- - Durable Functions
- summary: List a durable function's executions
+ - Functions
+ summary: Deploy multiple functions in one request
description: |
- Returns the platform's last observed status for each execution; listing
- does not poll each one. Fetch a single execution for its live state.
- operationId: listDurableExecutions
+ Upload multiple function source archives in one multipart request. Each archive should contain source files
+ plus dependency manifests/lockfiles, not installed dependency directories. ZIP and tar.gz uploads are
+ accepted and normalized to tar.gz before storage. The API enforces `SOURCE_ARCHIVE_SIZE_LIMIT_MB`
+ for each uploaded and normalized source archive. The server records a shared
+ deployment batch ID for the resulting function deployments. Each function deployment runs its own
+ compile/publish workflow concurrently, and each publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB`
+ for the final container image.
+ One batch request can include up to 100 functions. Submit multiple batch requests for larger projects.
+ If one function fails before its workflow starts, already-started function deployments are left
+ running and the failed function is reported in the `failed` array. Failed new functions are deleted;
+ failed updates are rolled back to their previous metadata/status where possible.
+ operationId: createFunctionsBatch
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - name: status
- in: query
- required: false
- description: Return only executions in this status.
- schema:
- $ref: '#/components/schemas/DurableExecutionStatus'
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ functions:
+ type: string
+ description: |
+ JSON array of functions with `name`, `runtime`, optional `handler`, and `file_field`. Each `file_field` must name a multipart file field containing that function's ZIP or tar.gz source bundle.
+
+ Each entry may also declare `variable_scope` (`all` or `scoped`) and `variables` (an array of project variable names). Omitting them leaves the function's stored declaration unchanged. Volcano detects direct environment references in the uploaded source code and keeps them separate from the declared names: detected names are not written back to the declaration and do not appear in a config export. A scoped function receives its declared names plus the detected ones the project defines; a detected name the project does not define is ignored, since such a reference is often optional. Detection reads code only, so a name appearing solely in a comment or in an unrelated string is not a reference. Declare a name when the function reads it through a computed key, or when it must not deploy without the variable. The request is rejected with 400 before anything is deployed if a scoped function declares a variable the project does not define, or if the resulting environment exceeds 4096 bytes.
+ code_0:
+ type: string
+ format: binary
+ description: Function ZIP or tar.gz archive referenced by the first manifest entry's `file_field`; additional code_N file fields may be included. Each archive is subject to SOURCE_ARCHIVE_SIZE_LIMIT_MB.
+ required:
+ - functions
responses:
- '200':
- description: Successful response
+ '202':
+ description: Batch deployment accepted
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedDurableExecutions'
+ $ref: '#/components/schemas/BatchFunctionDeployResponse'
+ '207':
+ description: Batch deployment partially accepted; successful functions started deployment and failed functions were compensated where possible
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BatchFunctionDeployResponse'
'400':
- description: Unsupported status filter
+ description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Durable function not found
+ '403':
+ description: Function limit exceeded for the project
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}/executions/{executionId}:
- get:
- tags:
- - Durable Functions
- summary: Get a durable execution
- description: |
- Returns the execution's current state, including its `result` once it has
- succeeded. Poll this to wait for an execution to finish.
- operationId: getDurableExecution
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/DurableExecutionId'
- responses:
- '200':
- description: Successful response
+ '409':
+ description: |
+ A name in the batch is held by a function of the other kind — a
+ function cannot change kind — or the project's source is managed by
+ Git, where deploys come from a push to the production branch.
content:
application/json:
schema:
- $ref: '#/components/schemas/DurableExecution'
- '404':
- description: Durable function or execution not found
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: |
+ The owner's billing-cycle build-minutes allowance is spent. The
+ error names the allowance and carries a link to the usage page.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
description: |
- Durable execution is not available in this environment. Returned by
- a deployment that has no durable execution engine, such as a local
- one; the request is not retryable there.
+ Build usage could not be read, so the allowance could not be
+ checked. The same request succeeds once it can be.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/durable-functions/{functionId}/executions/{executionId}/stop:
- post:
+ /projects/{id}/schedulers:
+ get:
tags:
- - Durable Functions
- summary: Stop a durable execution
+ - Functions
+ summary: List every function scheduler in a project
description: |
- Cancels a running execution. Its completed steps are not undone.
-
- The call is accepted rather than awaited: cancellation happens behind
- it, so the response reports the execution as it was read back and may
- still say `running`. Do not branch on that status — the execution
- settles into `stopped` shortly after, and polling
- `GET /projects/{id}/durable-functions/{functionId}/executions/{executionId}`
- is how you see it get there.
-
- Stopping an execution that already finished is not an error: the
- response carries the state it settled in.
- operationId: stopDurableExecution
+ Project-scoped counterpart to `/projects/{id}/functions/{functionId}/schedulers`.
+ Returns schedulers across all functions in the project, ordered by
+ creation time descending, with standard page/limit pagination so
+ clients don't have to fan out one request per function.
+ operationId: listProjectSchedulers
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DurableFunctionId'
- - $ref: '#/components/parameters/DurableExecutionId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
responses:
'200':
- description: |
- Stop accepted. The body is the execution as it was read back, which
- may still report `running`.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DurableExecution'
- '404':
- description: Durable function or execution not found
+ description: Project schedulers
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: Execution has not started yet
+ $ref: '#/components/schemas/FunctionSchedulerListResponse'
+ /projects/{id}/functions/{functionId}/schedulers:
+ get:
+ tags:
+ - Functions
+ summary: List schedulers for a function
+ operationId: listFunctionSchedulers
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FunctionId'
+ responses:
+ '200':
+ description: Function schedulers
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: |
- Durable execution is not available in this environment. Returned by
- a deployment that has no durable execution engine, such as a local
- one; the request is not retryable there.
+ $ref: '#/components/schemas/FunctionSchedulerListResponse'
+ '404':
+ description: Function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/frontends:
- get:
+ post:
tags:
- - Frontends
- summary: List all frontends in a project
- description: |
- Supports two mutually exclusive pagination modes. Offset mode uses `page`
- and `limit` and returns `next` (URL). Cursor mode uses `cursor` and
- `limit`, supports `search` (case-insensitive name match), and returns
- `next_cursor`. Sending both `page` and `cursor` (or `page` and `search`)
- returns 400.
- operationId: listFrontends
+ - Functions
+ summary: Create a scheduler for a function
+ description: Creates regional scheduled invocation jobs. Requested regions must be a subset of the function's deployed regions.
+ operationId: createFunctionScheduler
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/FunctionId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateFunctionSchedulerRequest'
responses:
- '200':
- description: Successful response
+ '201':
+ description: Scheduler created
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedFrontends'
+ $ref: '#/components/schemas/FunctionScheduler'
'400':
- description: Bad request - invalid project identifier
+ description: |
+ Invalid schedule, geofenced region, a scheduler of this name
+ already exists on the function, or the function is not active.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ '403':
+ description: |
+ Schedulers are not available on this plan, or the project already
+ holds as many as the plan allows. The cap counts standard and
+ durable function schedulers together.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ '404':
+ description: Function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ /projects/{id}/functions/{functionId}/schedulers/{schedulerId}:
+ get:
+ tags:
+ - Functions
+ summary: Get a function scheduler
+ operationId: getFunctionScheduler
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FunctionId'
+ - $ref: '#/components/parameters/SchedulerId'
+ responses:
+ '200':
+ description: Function scheduler
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ $ref: '#/components/schemas/FunctionScheduler'
+ '404':
+ description: Function or scheduler not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- post:
+ patch:
tags:
- - Frontends
- summary: Create a new frontend deployment
- description: |
- Creates and deploys a frontend for the project.
- If a frontend with the same name already exists in the project, this operation updates that
- frontend using the uploaded archive and starts a new deployment. A deployment that starts
- immediately returns `status: provisioning`, then transitions to `active`, `degraded`, or
- `failed`. If another deployment is running, the response preserves the frontend's current status
- and exposes the queued deployment through `pending_deployment_id`.
- Existing frontend traffic continues to use an available runtime while the new deployment builds
- and provisions. Each deployment publishes its own static assets before the runtimes switch to its
- build, and the live build's assets keep serving until the new deployment is live, so a page loaded
- mid-deployment resolves its assets whichever build served it. A failed redeploy puts the runtimes
- back on the build they were running, leaves the frontend `active` on the previous deployment, and
- records the attempted deployment as failed. `degraded` means the runtime remains available but
- edge synchronization requires recovery; Volcano retries the edge step without rebuilding. Only one deployment may run for a
- given frontend, while independent frontends and projects can deploy concurrently.
- For monorepos, provide `app_root` as a relative path from the uploaded archive root
- to the Next.js app that should be built. Omit it for single-app archives.
- Supported frontend environments are Next.js 15.x and 16.x with Node.js
- 22.x or 24.x. The Node.js runtime is inferred from
- `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x.
- The selected Node.js family must also satisfy the installed Next.js package's
- `engines.node` constraint. Volcano tests Next 15.5.25 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next 16.3.5 (`>=20.9.0`).
- Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
- does not apply its own source archive size limit. After the final container images are
- built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
- This operation is limited by plan-based frontend deployment quotas (`FREE_FRONTEND_DEPLOYMENTS`, `PRO_FRONTEND_DEPLOYMENTS`).
- Each project can contain up to 10,000 frontends regardless of plan.
- operationId: createFrontend
+ - Functions
+ summary: Update a function scheduler
+ operationId: updateFunctionScheduler
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FunctionId'
+ - $ref: '#/components/parameters/SchedulerId'
requestBody:
required: true
content:
- multipart/form-data:
+ application/json:
schema:
- type: object
- required:
- - name
- - archive
- properties:
- name:
- type: string
- maxLength: 63
- pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
- description: DNS-safe frontend name
- framework:
- type: string
- enum:
- - nextjs
- default: nextjs
- description: Next.js frontend. Supported Next.js majors are 15.x and 16.x.
- app_root:
- type: string
- maxLength: 1024
- description: Optional relative POSIX path from the uploaded archive root to the Next.js app to build, for example `apps/web`.
- example: apps/web
- archive:
- type: string
- format: binary
- description: ZIP or tar.gz archive of the frontend project directory or monorepo workspace root. The API enforces SOURCE_ARCHIVE_SIZE_LIMIT_MB and stores a normalized tar.gz archive.
+ $ref: '#/components/schemas/UpdateFunctionSchedulerRequest'
responses:
'200':
- description: Existing frontend updated; its deployment was started or queued
+ description: Scheduler updated
content:
application/json:
schema:
- $ref: '#/components/schemas/Frontend'
- '201':
- description: Frontend created and deployment workflow started
+ $ref: '#/components/schemas/FunctionScheduler'
+ '400':
+ description: |
+ Invalid schedule, geofenced region, or a scheduler of this name
+ already exists on the function.
content:
application/json:
schema:
- $ref: '#/components/schemas/Frontend'
- '400':
- description: Bad request
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Function or scheduler not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ delete:
+ tags:
+ - Functions
+ summary: Delete a function scheduler
+ operationId: deleteFunctionScheduler
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FunctionId'
+ - $ref: '#/components/parameters/SchedulerId'
+ responses:
+ '204':
+ description: Scheduler deleted
+ '404':
+ description: Function or scheduler not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Frontend deployment limit exceeded for the current plan or project hard cap
+ /projects/{id}/functions/{functionId}/deployments:
+ get:
+ tags:
+ - Functions
+ summary: List function deployments
+ operationId: listFunctionDeployments
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FunctionId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ responses:
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/PaginatedFunctionDeployments'
'404':
- description: Project not found
+ description: Function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: Conflict - frontend deletion is queued or running
+ /projects/{id}/durable-functions:
+ get:
+ tags:
+ - Durable Functions
+ summary: List all durable functions in a project
+ description: |
+ Standard functions never appear here, and durable functions never appear
+ under `/projects/{id}/functions`. The two are separate collections.
+ operationId: listDurableFunctions
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ $ref: '#/components/schemas/PaginatedDurableFunctions'
+ '400':
+ description: Bad request - invalid pagination parameters
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Service unavailable - frontend workflow or archive limit configuration missing
+ '404':
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/frontends/{frontendId}:
- get:
+ post:
tags:
- - Frontends
- summary: Get frontend details
- operationId: getFrontend
+ - Durable Functions
+ summary: Create or update a durable function
+ description: |
+ Upload a durable function source bundle. Creates the function on the
+ first call for a name and redeploys it on every call after that, the
+ same create-or-update contract `POST /projects/{id}/functions` has.
+
+ Volcano builds and deploys asynchronously. A deployment that starts
+ immediately returns `status: provisioning`, then transitions to `active`
+ or `failed`; a deployment that has to wait for a running one is exposed
+ through `pending_deployment_id`. Existing executions keep running
+ against the runtime they started on.
+
+ The `durable` configuration is derived from the project's plan rather
+ than supplied here, and is fixed once the function exists. A name
+ already held by a standard function is rejected with 409: a function
+ cannot change kind.
+ operationId: createDurableFunction
security:
- UserToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ required:
+ - name
+ - code
+ - runtime
+ properties:
+ name:
+ type: string
+ description: DNS-safe function name (lowercase letters, numbers, hyphens; cannot start or end with hyphen)
+ maxLength: 63
+ pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
+ example: order-pipeline
+ code:
+ type: string
+ format: binary
+ description: ZIP or tar.gz archive containing function source code plus dependency manifests/lockfiles.
+ runtime:
+ type: string
+ enum:
+ - nodejs22.x
+ - nodejs24.x
+ - python3.13
+ - python3.14
+ description: |
+ Runtime environment. Required. Durable execution needs the
+ durable authoring API, which ships for these runtimes only;
+ any other runtime is rejected with 400 and the response
+ names the ones that work. Note that a durable Python
+ function needs a newer runtime than a standard one defaults
+ to. `GET /functions/runtimes` reports `durable_capable` per
+ runtime.
+ example: nodejs24.x
+ handler:
+ type: string
+ description: The name of the function to invoke. Defaults to "handler" if not specified.
+ default: handler
+ example: handler
+ is_public:
+ type: boolean
+ description: |
+ Whether anon keys with `functions.invoke` may start an
+ execution. Redeploying is the only way to change it, since
+ the collection has no update endpoint; omit it to keep the
+ current visibility, and a new function starts private.
+
+ The standard collection's synchronous invocation fields —
+ `invocation_mode`, `http_auth_mode`, `openapi_spec` —
+ configure a request path no durable route serves, and are
+ rejected with 400 rather than ignored.
+ variable_scope:
+ type: string
+ enum:
+ - all
+ - scoped
+ x-enum-varnames:
+ - CreateDurableFunctionMultipartBodyVariableScopeAll
+ - CreateDurableFunctionMultipartBodyVariableScopeScoped
+ description: |
+ Which project variables this function receives. `all` (the default) gives it every project variable; `scoped` gives it only the variables it selects. Omitting this leaves an existing function's scope unchanged.
+ variables:
+ type: string
+ description: |
+ JSON-encoded array of project variable names this function requires, on top of the ones detected in its source. A declared name the project does not define is rejected with 400; a detected name it does not define is ignored. Only used when `variable_scope` is `scoped`. Omitting this leaves an existing function's declared names unchanged.
responses:
'200':
- description: Successful response
+ description: Existing durable function updated; its deployment was started or queued
content:
application/json:
schema:
- $ref: '#/components/schemas/Frontend'
- '400':
- description: Bad request - invalid identifier
+ $ref: '#/components/schemas/DurableFunction'
+ '201':
+ description: Durable function created and deployment workflow started
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ $ref: '#/components/schemas/DurableFunction'
+ '400':
+ description: |
+ Bad request (invalid archive, unsupported runtime, invalid name, or a
+ project region that does not offer durable execution — a durable
+ function deploys to every region of its project)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - project ownership required
+ description: Durable function limit exceeded for the project
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Frontend not found
+ '409':
+ description: Name is held by a standard function, or a deletion is queued or running
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
- description: Internal server error
+ description: Internal server error (function deployment failed)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ '503':
+ description: |
+ Durable deploys are paused platform-wide. The same request succeeds
+ once they are re-enabled; executions already running are unaffected.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/durable-functions/{functionId}:
+ get:
tags:
- - Frontends
- summary: Delete a frontend
- description: |
- Schedules asynchronous frontend deletion. If another deployment is running, the frontend
- preserves its current status and exposes the queued deletion through `pending_deployment_id`.
- Its status changes to `deleting` when cleanup starts. After cleanup, it returns 404 and no
- longer appears in frontend lists.
- operationId: deleteFrontend
+ - Durable Functions
+ summary: Get durable function by ID or name
+ operationId: getDurableFunction
security:
- UserToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/DurableFunctionId'
responses:
- '202':
- description: Frontend deletion started or queued
- '400':
- description: Bad request - invalid identifier
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ $ref: '#/components/schemas/DurableFunction'
+ '404':
+ description: Durable function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ delete:
+ tags:
+ - Durable Functions
+ summary: Delete a durable function
+ description: |
+ Accepted for asynchronous teardown; the work continues after the
+ response. The function's executions go with it: executions still in
+ flight are stopped, and history stops being readable whatever
+ `retention_days` had left.
+
+ Stopping is asynchronous at the platform, and it does not interrupt a
+ step already running -- that step runs to its next checkpoint. So a
+ delete ends an execution rather than halting it mid-step; stop the
+ execution yourself first if you need to observe it ending.
+ operationId: deleteDurableFunction
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ responses:
+ '202':
+ description: Deletion accepted and teardown started
'404':
- description: Frontend not found
+ description: |
+ Durable function not found. Also returned for an id that names a
+ durable function in another project, so the response cannot be used
+ to tell the two apart.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ /projects/{id}/durable-functions/{functionId}/deployments:
+ get:
+ tags:
+ - Durable Functions
+ summary: List durable function deployments
+ operationId: listDurableFunctionDeployments
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ responses:
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Service unavailable - frontend workflow configuration missing
+ $ref: '#/components/schemas/PaginatedFunctionDeployments'
+ '404':
+ description: Durable function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/frontends/{frontendId}/redeploy:
- post:
+ /projects/{id}/durable-functions/{functionId}/schedulers:
+ get:
tags:
- - Frontends
- summary: Redeploy frontend using latest uploaded artifact
+ - Durable Functions
+ summary: List schedulers for a durable function
description: |
- Starts a new frontend workflow using the latest stored artifact. A deployment that starts
- immediately returns `status: provisioning`, then transitions to `active`, `degraded`, or
- `failed`. An overlapping deployment preserves the frontend's current status, is exposed through
- `pending_deployment_id`, and supersedes any older queued deployment. The previous runtime and its
- published static assets remain available during provisioning, and a failed redeploy restores the
- regional runtimes to that build and keeps it serving while the attempted deployment is recorded as
- failed.
- operationId: redeployFrontend
+ The durable collection's counterpart to
+ `/projects/{id}/functions/{functionId}/schedulers`. A standard
+ function's id is not accepted here, and a durable function's id is not
+ accepted there.
+ operationId: listDurableFunctionSchedulers
security:
- UserToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/DurableFunctionId'
responses:
'200':
- description: Frontend redeploy started or queued
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Frontend'
- '400':
- description: Bad request - invalid identifier
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ description: Durable function schedulers
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ $ref: '#/components/schemas/FunctionSchedulerListResponse'
+ '404':
+ description: Durable function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Frontend not found
+ post:
+ tags:
+ - Durable Functions
+ summary: Create a scheduler for a durable function
+ description: |
+ Each tick starts an execution rather than invoking the function, under
+ an execution name derived from the run, so a retried tick resolves to
+ the execution it already started. Requested regions must be a subset of
+ the function's deployed regions.
+
+ A tick draws on the same durable allowances and concurrency cap a
+ manual start does, and a tick that would exceed the cap fails that run.
+ operationId: createDurableFunctionScheduler
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateFunctionSchedulerRequest'
+ responses:
+ '201':
+ description: Scheduler created
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: Conflict - frontend deletion is queued or running
+ $ref: '#/components/schemas/FunctionScheduler'
+ '400':
+ description: |
+ Invalid schedule, geofenced region, a scheduler of this name
+ already exists on the function, or the function is not active.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '403':
+ description: |
+ Schedulers are not available on this plan, or the project already
+ holds as many as the plan allows. The cap counts standard and
+ durable function schedulers together.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Service unavailable - frontend workflow configuration missing
+ '404':
+ description: Durable function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/frontends/{frontendId}/domain:
+ /projects/{id}/durable-functions/{functionId}/schedulers/{schedulerId}:
get:
tags:
- - Frontends
- summary: Get frontend custom domain status
- operationId: getFrontendCustomDomain
+ - Durable Functions
+ summary: Get a durable function scheduler
+ operationId: getDurableFunctionScheduler
security:
- UserToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/SchedulerId'
responses:
'200':
- description: |
- Frontend custom domain status, or null when the frontend has no
- custom domain configured (the common empty state).
- content:
- application/json:
- schema:
- nullable: true
- allOf:
- - $ref: '#/components/schemas/FrontendCustomDomainResponse'
- '400':
- description: Bad request - invalid identifier
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ description: Durable function scheduler
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/FunctionScheduler'
'404':
- description: Frontend not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ description: Durable function or scheduler not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- post:
+ patch:
tags:
- - Frontends
- summary: Configure frontend custom domain (PRO)
- description: |
- Configures one custom domain for a frontend.
- The default Volcano-generated frontend URL remains active.
- Wildcard Volcano frontend TLS remains valid and isolated from custom-domain certificate changes.
- operationId: createFrontendCustomDomain
+ - Durable Functions
+ summary: Update a durable function scheduler
+ operationId: updateDurableFunctionScheduler
security:
- UserToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/SchedulerId'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateFrontendCustomDomainRequest'
+ $ref: '#/components/schemas/UpdateFunctionSchedulerRequest'
responses:
'200':
- description: Custom domain already configured with same hostname
+ description: Scheduler updated
content:
application/json:
schema:
- $ref: '#/components/schemas/FrontendCustomDomainResponse'
- '201':
- description: Custom domain provisioning started
+ $ref: '#/components/schemas/FunctionScheduler'
+ '400':
+ description: |
+ Invalid schedule, geofenced region, or a scheduler of this name
+ already exists on the function.
content:
application/json:
schema:
- $ref: '#/components/schemas/FrontendCustomDomainResponse'
- '400':
- description: Bad request - invalid domain
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Durable function or scheduler not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ delete:
+ tags:
+ - Durable Functions
+ summary: Delete a durable function scheduler
+ operationId: deleteDurableFunctionScheduler
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/SchedulerId'
+ responses:
+ '204':
+ description: Scheduler deleted
+ '404':
+ description: Durable function or scheduler not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - custom domains require PRO plan
+ /projects/{id}/durable-functions/{functionId}/executions:
+ post:
+ tags:
+ - Durable Functions
+ summary: Start a durable execution
+ description: |
+ Starts an execution and returns its handle. Never returns a result: an
+ execution can outlive any request a client could hold open, so the
+ result is read back from
+ `GET /projects/{id}/durable-functions/{functionId}/executions/{executionId}`.
+
+ The request body is the execution's input and must be valid JSON if
+ present. An empty body starts the execution with no input.
+
+ Send `X-Volcano-Execution-Name` to make the start idempotent: repeating a
+ start with the same name returns the existing execution instead of
+ beginning a second one.
+
+ Each execution counts against the project's durable execution
+ allowance, the operations it performs count against the durable
+ operations allowance when it finishes, and the number of executions in
+ flight at once is capped by the plan.
+ operationId: startDurableExecution
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - name: X-Volcano-Execution-Name
+ in: header
+ required: false
+ description: |
+ Idempotency key for this execution. Generated when omitted. A repeat
+ under a name that already names a running execution returns that
+ execution and is not charged again.
+
+ Letters, digits, `-`, `_` and `.`, up to 255 characters. Anything
+ else is rejected with `400`.
+ schema:
+ type: string
+ maxLength: 255
+ pattern: ^[A-Za-z0-9._-]+$
+ requestBody:
+ required: false
+ content:
+ application/json:
+ schema:
+ description: Input passed to the function, up to 256 KiB.
+ responses:
+ '202':
+ description: Execution accepted and started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DurableExecution'
+ '400':
+ description: Payload is not valid JSON, or the execution name is invalid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Frontend not found
+ description: Durable function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: Conflict - custom domain already in use, still detaching, or frontend already has a custom domain
+ description: |
+ Function is not deployed yet, or has no deployed region. Also
+ returned when two starts under the same execution name raced and
+ both released it, which is retryable as it stands.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '413':
+ description: Payload exceeds the maximum execution input size
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: |
+ Too many executions already in flight for this project, or the
+ account is out of one of its billing-cycle durable allowances:
+ executions, operations, or compute. An owner-started execution is
+ metered exactly like an application-started one.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Service unavailable - custom domain provisioning is temporarily unavailable
+ description: |
+ Durable execution is not available in this environment, or the
+ plan terms for the start could not be read. The first means the
+ capability is paused or this deployment cannot serve it, so it is
+ not one to retry in a loop; the second is transient.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ get:
tags:
- - Frontends
- summary: Delete frontend custom domain
- operationId: deleteFrontendCustomDomain
+ - Durable Functions
+ summary: List a durable function's executions
+ description: |
+ Returns the platform's last observed status for each execution; listing
+ does not poll each one. Fetch a single execution for its live state.
+ operationId: listDurableExecutions
security:
- UserToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - name: status
+ in: query
+ required: false
+ description: Return only executions in this status.
+ schema:
+ $ref: '#/components/schemas/DurableExecutionStatus'
responses:
- '204':
- description: Custom domain detach scheduled
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PaginatedDurableExecutions'
'400':
- description: Bad request - invalid identifier
+ description: Unsupported status filter
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized - invalid or missing token
+ '404':
+ description: Durable function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Forbidden - project ownership required
+ /projects/{id}/durable-functions/{functionId}/executions/{executionId}:
+ get:
+ tags:
+ - Durable Functions
+ summary: Get a durable execution
+ description: |
+ Returns the execution's current state, including its `result` once it has
+ succeeded. Poll this to wait for an execution to finish.
+ operationId: getDurableExecution
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/DurableExecutionId'
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DurableExecution'
+ '404':
+ description: Durable function or execution not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: |
+ Durable execution is not available in this environment. Either the
+ capability is paused or this deployment cannot serve it, so the
+ request is not one to retry in a loop.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ /projects/{id}/durable-functions/{functionId}/executions/{executionId}/stop:
+ post:
+ tags:
+ - Durable Functions
+ summary: Stop a durable execution
+ description: |
+ Cancels a running execution. Its completed steps are not undone.
+
+ The call is accepted rather than awaited: cancellation happens behind
+ it, so the response reports the execution as it was read back and may
+ still say `running`. Do not branch on that status — the execution
+ settles into `stopped` shortly after, and polling
+ `GET /projects/{id}/durable-functions/{functionId}/executions/{executionId}`
+ is how you see it get there.
+
+ Stopping an execution that already finished is not an error: the
+ response carries the state it settled in.
+ operationId: stopDurableExecution
+ security:
+ - UserToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DurableFunctionId'
+ - $ref: '#/components/parameters/DurableExecutionId'
+ responses:
+ '200':
+ description: |
+ Stop accepted. The body is the execution as it was read back, which
+ may still report `running`.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DurableExecution'
'404':
- description: Frontend or custom domain not found
+ description: Durable function or execution not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '409':
+ description: Execution has not started yet
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/frontends/{frontendId}/deployments:
+ '503':
+ description: |
+ Durable execution is not available in this environment. Either the
+ capability is paused or this deployment cannot serve it, so the
+ request is not one to retry in a loop.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/frontends:
get:
tags:
- Frontends
- summary: List frontend deployments
- operationId: listFrontendDeployments
+ summary: List all frontends in a project
+ description: |
+ Supports two mutually exclusive pagination modes. Offset mode uses `page`
+ and `limit` and returns `next` (URL). Cursor mode uses `cursor` and
+ `limit`, supports `search` (case-insensitive name match), and returns
+ `next_cursor`. Sending both `page` and `cursor` (or `page` and `search`)
+ returns 400.
+ operationId: listFrontends
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
responses:
'200':
description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedFrontendDeployments'
+ $ref: '#/components/schemas/PaginatedFrontends'
'400':
- description: Bad request - invalid identifier
+ description: Bad request - invalid project identifier
content:
application/json:
schema:
@@ -4949,7 +5307,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Frontend not found
+ description: Project not found
content:
application/json:
schema:
@@ -4960,45 +5318,100 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/frontends/{frontendId}/usage:
- get:
+ post:
tags:
- Frontends
- summary: Per-day request and error counts for a single frontend
+ summary: Create a new frontend deployment
description: |
- Returns a zero-filled daily series of request counts and 5xx
- error counts for one frontend, oldest first. Each entry is one
- UTC day; missing days (no traffic recorded) come back as
- `requests: 0, errors: 0` so the response always has exactly
- `days` entries.
-
- Backs the Monitoring section on the Frontend detail page in
- volcano-web. `days` defaults to 30 and is capped at 90 to keep
- the (frontend_id, day) index scan bounded.
- operationId: getFrontendUsageHistory
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/FrontendId'
- - name: days
- in: query
- description: Number of trailing days to return (1–90, default 30).
- required: false
- schema:
- type: integer
- minimum: 1
- maximum: 90
- default: 30
+ Creates and deploys a frontend for the project.
+ If a frontend with the same name already exists in the project, this operation updates that
+ frontend using the uploaded archive and starts a new deployment. A deployment that starts
+ immediately returns `status: provisioning`, then transitions to `active`, `degraded`, or
+ `failed`. If another deployment is running, the response preserves the frontend's current status
+ and exposes the queued deployment through `pending_deployment_id`.
+ Existing frontend traffic continues to use an available runtime while the new deployment builds
+ and provisions. Each deployment publishes its own static assets before the runtimes switch to its
+ build, and the live build's assets keep serving until the new deployment is live, so a page loaded
+ mid-deployment resolves its assets whichever build served it. A failed redeploy puts the runtimes
+ back on the build they were running, leaves the frontend `active` on the previous deployment, and
+ records the attempted deployment as failed. `degraded` means the runtime remains available but
+ edge synchronization requires recovery; Volcano retries the edge step without rebuilding. Only one deployment may run for a
+ given frontend, while independent frontends and projects can deploy concurrently.
+ For monorepos, provide `app_root` as a relative path from the uploaded archive root
+ to the Next.js app that should be built. Omit it for single-app archives.
+ Supported frontend environments are Next.js 15.x and 16.x with Node.js
+ 22.x or 24.x. The Node.js runtime is inferred from
+ `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x.
+ The selected Node.js family must also satisfy the installed Next.js package's
+ `engines.node` constraint. Volcano tests Next 15.5.26 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next 16.3.6 (`>=20.9.0`).
+ Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
+ does not apply its own source archive size limit. After the final container images are
+ built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
+ This operation is limited by plan-based frontend deployment quotas (`FREE_FRONTEND_DEPLOYMENTS`, `PRO_FRONTEND_DEPLOYMENTS`).
+ Each project can contain up to 10,000 frontends regardless of plan.
+ operationId: createFrontend
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ required:
+ - name
+ - archive
+ properties:
+ name:
+ type: string
+ maxLength: 63
+ pattern: ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
+ description: DNS-safe frontend name
+ framework:
+ type: string
+ enum:
+ - nextjs
+ default: nextjs
+ description: Next.js frontend. Supported Next.js majors are 15.x and 16.x.
+ app_root:
+ type: string
+ maxLength: 1024
+ description: Optional relative POSIX path from the uploaded archive root to the Next.js app to build, for example `apps/web`.
+ example: apps/web
+ variable_scope:
+ type: string
+ enum:
+ - all
+ - scoped
+ description: Variable selection for this deployment. New frontends default to `scoped`; omitting this field for an existing frontend preserves its current selection.
+ variables:
+ type: array
+ items:
+ type: string
+ pattern: ^[A-Za-z_][A-Za-z0-9_]*$
+ description: Project variable names selected when `variable_scope` is `scoped`. Submit each name as a repeated multipart field.
+ archive:
+ type: string
+ format: binary
+ description: ZIP or tar.gz archive of the frontend project directory or monorepo workspace root. The API enforces SOURCE_ARCHIVE_SIZE_LIMIT_MB and stores a normalized tar.gz archive.
responses:
'200':
- description: Successful response
+ description: Existing frontend updated; its deployment was started or queued
content:
application/json:
schema:
- $ref: '#/components/schemas/FrontendUsageHistoryResponse'
+ $ref: '#/components/schemas/Frontend'
+ '201':
+ description: Frontend created and deployment workflow started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Frontend'
'400':
- description: Bad request - invalid identifier
+ description: Bad request
content:
application/json:
schema:
@@ -5010,13 +5423,19 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Forbidden - project ownership required
+ description: Frontend deployment limit exceeded for the current plan or project hard cap
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Frontend not found
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: Conflict - frontend deletion is queued or running
content:
application/json:
schema:
@@ -5027,2654 +5446,2402 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/variables:
+ '503':
+ description: Service unavailable - frontend workflow or archive limit configuration missing
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/frontends/{frontendId}:
get:
tags:
- - Variables
- summary: List all variables for a project
- description: |
- Returns project-level environment variables used by deployed functions and frontends.
- operationId: listVariables
+ - Frontends
+ summary: Get frontend details
+ operationId: getFrontend
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/FrontendId'
responses:
'200':
description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedVariables'
- '404':
- description: Project not found
+ $ref: '#/components/schemas/Frontend'
+ '400':
+ description: Bad request - invalid identifier
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- post:
- tags:
- - Variables
- summary: Create or update a variable
- description: |
- Creates a project-level environment variable and triggers asynchronous propagation
- to deployed functions and frontends in the project's configured regions.
- operationId: createVariable
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/CreateVariableRequest'
- responses:
- '201':
- description: Variable created
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- $ref: '#/components/schemas/Variable'
- '400':
- description: Bad request
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Private variable membership writes are disabled during rollout
+ '404':
+ description: Frontend not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases:
- get:
- tags:
- - Databases
- summary: List all databases for a project
- description: |
- Supports two mutually exclusive pagination modes. Offset mode uses `page`
- and `limit`. Cursor mode uses `cursor` and `limit`, supports `search`
- (case-insensitive name match), and returns `next_cursor`/`prev_cursor`.
- The optional `status` filter applies in both modes and is bound to the
- cursor. Sending both `page` and `cursor` (or `page` and `search`) returns 400.
- operationId: listDatabases
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
- - name: status
- in: query
- required: false
- schema:
- type: string
- enum:
- - provisioning
- - active
- - restoring
- - failed
- - deleting
- description: Return only the databases in this status.
- responses:
- '200':
- description: Successful response
+ '500':
+ description: Internal server error
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedDatabases'
- post:
+ $ref: '#/components/schemas/Error'
+ delete:
tags:
- - Databases
- summary: Create a new serverless PostgreSQL database
+ - Frontends
+ summary: Delete a frontend
description: |
- Creates a serverless PostgreSQL database in the project.
- Each project can hold 1 database on Free and up to 10,000 on Pro.
- Requests over the plan's cap return 403.
- operationId: createDatabase
+ Schedules asynchronous frontend deletion. If another deployment is running, the frontend
+ preserves its current status and exposes the queued deletion through `pending_deployment_id`.
+ Its status changes to `deleting` when cleanup starts. After cleanup, it returns 404 and no
+ longer appears in frontend lists.
+ operationId: deleteFrontend
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/CreateDatabaseRequest'
+ - $ref: '#/components/parameters/FrontendId'
responses:
- '201':
- description: Database created (provisioning)
+ '202':
+ description: Frontend deletion started or queued
+ '400':
+ description: Bad request - invalid identifier
content:
application/json:
schema:
- $ref: '#/components/schemas/Database'
- '403':
- description: Database limit exceeded for the project
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}:
- get:
- tags:
- - Databases
- summary: Get database details
- operationId: getDatabase
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- responses:
- '200':
- description: Successful response
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
- $ref: '#/components/schemas/Database'
- delete:
- tags:
- - Databases
- summary: Delete a database
- description: |
- Deletes a database and the instance backing it. When the instance is
- removed synchronously the database row is deleted and the response is
- `204`. If the instance cannot be deleted right away, the database row
- is retained (status `deleting`) and its teardown is handed to the
- background reconciler, which retries the deletion and removes the row
- once the instance is gone; in that case the response is `202`. The database row is
- never dropped while its instance still exists, so an instance is
- never orphaned without a record to retry from.
- operationId: deleteDatabase
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- responses:
- '202':
- description: |
- Deletion accepted and in progress. The backing instance could not be
- removed synchronously, so the database is marked `deleting` and torn
- down asynchronously by the reconciler.
- content:
- application/json:
- schema:
- type: object
- properties:
- status:
- type: string
- example: deleting
- message:
- type: string
- example: database deletion in progress
- '204':
- description: Database deleted (backing instance removed synchronously)
+ $ref: '#/components/schemas/Error'
'404':
- description: Project or database not found
+ description: Frontend not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: |
- A restore is running on the database. Deleting it while a worker is
- replacing its data would race that worker, so wait for the restore
- to finish.
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: |
- Volcano could not check whether a restore is running, and will not
- delete a database that might be mid-restore. Retry.
+ description: Service unavailable - frontend workflow configuration missing
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/branches:
- get:
+ /projects/{id}/frontends/{frontendId}/redeploy:
+ post:
tags:
- - Database Branches
- summary: List a database's branches
+ - Frontends
+ summary: Redeploy frontend using latest uploaded artifact
description: |
- Returns every branch of the database, including those still provisioning
- and those that failed, since each still holds a name.
-
- Connection strings are omitted. Fetch a single branch to get its
- connection string.
- operationId: listDatabaseBranches
+ Starts a new frontend workflow using the latest stored artifact. A deployment that starts
+ immediately returns `status: provisioning`, then transitions to `active`, `degraded`, or
+ `failed`. An overlapping deployment preserves the frontend's current status, is exposed through
+ `pending_deployment_id`, and supersedes any older queued deployment. The previous runtime and its
+ published static assets remain available during provisioning, and a failed redeploy restores the
+ regional runtimes to that build and keeps it serving while the attempted deployment is recorded as
+ failed.
+ operationId: redeployFrontend
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/FrontendId'
responses:
'200':
- description: Successful response
+ description: Frontend redeploy started or queued
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBranchList'
- '404':
- description: Project or database not found
+ $ref: '#/components/schemas/Frontend'
+ '400':
+ description: Bad request - invalid identifier
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Branching is temporarily unavailable
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- post:
- tags:
- - Database Branches
- summary: Create a branch of a database
- description: |
- Forks the database into a new branch. The branch starts as an exact copy
- of the parent's data and diverges from there.
-
- Provisioning is asynchronous: the response is `202` with the branch in
- `provisioning` and no connection string. Poll the branch until it reports
- `active`, at which point it carries its own connection string.
-
- Retrying a create with a name that already exists returns `409` rather
- than a second branch, so a retried request cannot silently consume two
- slots of the branch allowance.
- operationId: createDatabaseBranch
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/CreateDatabaseBranchRequest'
- responses:
- '202':
- description: Branch accepted and provisioning
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseBranch'
- '400':
- description: Invalid branch name or lifetime
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: |
- The database has reached its branch allowance, or the owner's plan
- does not include branching.
+ '404':
+ description: Frontend not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project or database not found
+ '409':
+ description: Conflict - frontend deletion is queued or running
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: |
- A branch of that name already exists on this database, or the
- database cannot be branched right now because it is still
- provisioning, being restored, failed, or being deleted.
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Branching is temporarily unavailable
+ description: Service unavailable - frontend workflow configuration missing
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/branches/{branchName}:
+ /projects/{id}/frontends/{frontendId}/domain:
get:
tags:
- - Database Branches
- summary: Get a branch
- description: |
- Returns the branch, including its connection string once it is `active`.
- Poll this after creating a branch to learn when it is connectable.
- operationId: getDatabaseBranch
+ - Frontends
+ summary: Get frontend custom domain status
+ operationId: getFrontendCustomDomain
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
+ - $ref: '#/components/parameters/FrontendId'
responses:
'200':
- description: Successful response
+ description: |
+ Frontend custom domain status, or null when the frontend has no
+ custom domain configured (the common empty state).
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBranch'
+ nullable: true
+ allOf:
+ - $ref: '#/components/schemas/FrontendCustomDomainResponse'
+ '400':
+ description: Bad request - invalid identifier
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - project ownership required
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Project, database, or branch not found
+ description: Frontend not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Branching is temporarily unavailable
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- patch:
+ post:
tags:
- - Database Branches
- summary: Extend a branch's lifetime
+ - Frontends
+ summary: Configure frontend custom domain (PRO)
description: |
- Replaces the branch's lifetime and restarts the countdown from now, so a
- branch you are still working on is not swept mid-session. The new
- duration is remembered, so a later reset re-arms the same lifetime.
- operationId: updateDatabaseBranch
+ Configures one custom domain for a frontend.
+ The default Volcano-generated frontend URL remains active.
+ Wildcard Volcano frontend TLS remains valid and isolated from custom-domain certificate changes.
+ operationId: createFrontendCustomDomain
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
+ - $ref: '#/components/parameters/FrontendId'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateDatabaseBranchRequest'
+ $ref: '#/components/schemas/CreateFrontendCustomDomainRequest'
responses:
'200':
- description: Lifetime updated
+ description: Custom domain already configured with same hostname
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBranch'
+ $ref: '#/components/schemas/FrontendCustomDomainResponse'
+ '201':
+ description: Custom domain provisioning started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FrontendCustomDomainResponse'
'400':
- description: Requested lifetime is outside the allowed range
+ description: Bad request - invalid domain
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - custom domains require PRO plan
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project, database, or branch not found
+ description: Frontend not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: The branch is being deleted
+ description: Conflict - custom domain already in use, still detaching, or frontend already has a custom domain
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Branching is temporarily unavailable
+ description: Service unavailable - custom domain provisioning is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- - Database Branches
- summary: Delete a branch
- description: |
- Marks the branch for teardown and returns immediately. The branch stops
- accepting connections at once; its fork and its row are removed by a
- background job, so a provider outage cannot leave the call hanging or the
- branch half-deleted.
-
- Deleting a branch that is still provisioning is allowed and stops the
- build, and repeating the call while teardown is in progress is accepted
- again. Once the branch is gone the call returns `404`.
- operationId: deleteDatabaseBranch
+ - Frontends
+ summary: Delete frontend custom domain
+ operationId: deleteFrontendCustomDomain
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
+ - $ref: '#/components/parameters/FrontendId'
responses:
- '202':
- description: Deletion accepted and in progress
+ '204':
+ description: Custom domain detach scheduled
+ '400':
+ description: Bad request - invalid identifier
content:
application/json:
schema:
- type: object
- properties:
- status:
- type: string
- example: deleting
- message:
- type: string
- example: branch deletion in progress
- required:
- - status
- - message
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - project ownership required
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Project, database, or branch not found
+ description: Frontend or custom domain not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Branching is temporarily unavailable
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/branches/{branchName}/reset:
- post:
+ /projects/{id}/frontends/{frontendId}/deployments:
+ get:
tags:
- - Database Branches
- summary: Reset a branch to its parent's current state
- description: |
- Discards everything written on the branch and re-forks it from the
- parent as it is now.
-
- Returns immediately with the branch in `provisioning`. The rewind runs in
- the background; poll the branch until it reports `active` before
- connecting again.
-
- The branch keeps its name and its connection string, so anything holding
- that string keeps working once it is active again, and its lifetime is
- re-armed to the duration it was created with. The branch does not serve
- connections for the duration of the reset.
- operationId: resetDatabaseBranch
+ - Frontends
+ summary: List frontend deployments
+ operationId: listFrontendDeployments
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
+ - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
responses:
- '202':
- description: Branch reset accepted; the branch is provisioning
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBranch'
- '404':
- description: Project, database, or branch not found
+ $ref: '#/components/schemas/PaginatedFrontendDeployments'
+ '400':
+ description: Bad request - invalid identifier
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: |
- The branch is not active, a reset is already in progress, the parent
- database is being restored, or the parent was restored within the
- last 24 hours — a reset re-forks from the parent, and the provider
- holds a child's reset shut for that long afterwards.
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Branching is temporarily unavailable
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/branches/{branchName}/reset-password:
- post:
+ '404':
+ description: Frontend not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/frontends/{frontendId}/usage:
+ get:
tags:
- - Database Branches
- summary: Rotate a branch's password
+ - Frontends
+ summary: Per-day request and error counts for a single frontend
description: |
- Issues a new password for the branch and invalidates the previous
- connection string. Existing connections are not interrupted; new ones
- must use the returned string. Proxies pick the rotation up within a few
- seconds, so the previous password can still open new connections until
- then.
+ Returns a zero-filled daily series of request counts and 5xx
+ error counts for one frontend, oldest first. Each entry is one
+ UTC day; missing days (no traffic recorded) come back as
+ `requests: 0, errors: 0` so the response always has exactly
+ `days` entries.
- The parent database's credentials are untouched.
- operationId: resetDatabaseBranchPassword
+ Backs the Monitoring section on the Frontend detail page in
+ volcano-web. `days` defaults to 30 and is capped at 90 to keep
+ the (frontend_id, day) index scan bounded.
+ operationId: getFrontendUsageHistory
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
+ - $ref: '#/components/parameters/FrontendId'
+ - name: days
+ in: query
+ description: Number of trailing days to return (1–90, default 30).
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 90
+ default: 30
responses:
'200':
- description: Password rotated
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBranch'
- '404':
- description: Project, database, or branch not found
+ $ref: '#/components/schemas/FrontendUsageHistoryResponse'
+ '400':
+ description: Bad request - invalid identifier
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: The branch is not active
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Branching is temporarily unavailable
+ '403':
+ description: Forbidden - project ownership required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/backups:
+ '404':
+ description: Frontend not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/frontends/{frontendId}/function-routes:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FrontendId'
get:
tags:
- - Database Backups
- summary: List a database's backups
- description: |
- Returns every backup of the database, newest first, together with the
- window a point-in-time restore may target.
-
- Both backups you took and backups the schedule produced are listed;
- `source` tells them apart. Only manual backups count against the plan's
- backup allowance.
- operationId: listDatabaseBackups
+ - Frontends
+ summary: List a Frontend's Function routes
+ operationId: listFrontendFunctionRoutes
security:
- UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
+ - ProjectAccessToken: []
responses:
'200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseBackupList'
- '403':
- description: Backups are PRO-only and the owner's plan does not include them
+ description: Function routes ordered from most to least specific
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project or database not found
+ $ref: '#/components/schemas/FrontendFunctionRouteList'
+ '401':
+ description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: |
- The database has no storage project yet, so there is nothing to
- list. A database reports this while it is still provisioning.
+ '403':
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Backups are temporarily unavailable
+ '500':
+ description: Failed to list Function routes
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- - Database Backups
- summary: Back up a database
- description: |
- Captures the database as it is now. The backup is available immediately;
- its `size_bytes` appears once the storage provider has costed it.
-
- Backups are rate-limited to one per minute per database, and capped by
- the owner's plan.
- operationId: createDatabaseBackup
+ - Frontends
+ summary: Route a Frontend path to an HTTP Function
+ operationId: createFrontendFunctionRoute
+ description: The Frontend and Function must belong to this Project. The Function may be private but must use HTTP invocation mode. The route applies to every hostname that resolves to the Frontend, including generated, custom-domain, preview, and local hostnames.
security:
- UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
+ - ProjectAccessToken: []
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateDatabaseBackupRequest'
+ $ref: '#/components/schemas/CreateFrontendFunctionRouteRequest'
responses:
'201':
- description: Backup created
+ description: Function route created
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBackup'
+ $ref: '#/components/schemas/FrontendFunctionRoute'
'400':
- description: Invalid backup name
+ description: Invalid path or non-HTTP Function
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: |
- The database has reached its backup allowance, or the owner's plan
- does not include backups, which are PRO-only.
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project or database not found
+ description: Frontend or Function not found in the Project
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: |
- A backup of that name already exists, the database is not active, a
- restore is running on it, or a backup was taken too recently.
+ description: Path is already routed or the Frontend has reached its 64-route limit
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Backups are temporarily unavailable
+ '500':
+ description: Failed to create Function route
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/backups/{backupName}:
- get:
+ /projects/{id}/frontends/{frontendId}/function-routes/{routeId}:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/FrontendId'
+ - $ref: '#/components/parameters/FrontendFunctionRouteId'
+ put:
tags:
- - Database Backups
- summary: Get a backup
- description: Returns one backup of the database.
- operationId: getDatabaseBackup
+ - Frontends
+ summary: Replace a Frontend Function route
+ operationId: updateFrontendFunctionRoute
security:
- UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BackupName'
- responses:
- '200':
- description: Successful response
+ - ProjectAccessToken: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateFrontendFunctionRouteRequest'
+ responses:
+ '200':
+ description: Function route replaced
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBackup'
+ $ref: '#/components/schemas/FrontendFunctionRoute'
+ '400':
+ description: Invalid path or non-HTTP Function
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: Backups are PRO-only and the owner's plan does not include them
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project, database, or backup not found
+ description: Function route, Frontend, or Function not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: |
- The database has no storage project yet, so it holds no backups. A
- database reports this while it is still provisioning.
+ description: Path is already routed or the Frontend has reached its 64-route limit
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Backups are temporarily unavailable
+ '500':
+ description: Failed to replace Function route
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- - Database Backups
- summary: Delete a backup
- description: |
- Deletes the backup and frees its storage. Scheduled backups can be
- deleted too. A backup that is already gone reports `404`, so a name
- that never existed and a name that no longer does read the same.
- Refused with `409` while the database is being restored.
- operationId: deleteDatabaseBackup
+ - Frontends
+ summary: Delete a Frontend Function route
+ operationId: deleteFrontendFunctionRoute
security:
- UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BackupName'
+ - ProjectAccessToken: []
responses:
- '200':
- description: Backup deleted
+ '204':
+ description: Function route deleted
+ '401':
+ description: Unauthorized
content:
application/json:
schema:
- type: object
- properties:
- status:
- type: string
- example: deleted
- message:
- type: string
- example: backup deleted
- required:
- - status
- - message
+ $ref: '#/components/schemas/Error'
'403':
- description: Backups are PRO-only and the owner's plan does not include them
+ description: Project not owned by the caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project, database, or backup not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: |
- The database is being restored. A restore is pinned to a backup it
- may not have restored yet, so deleting one is refused until the
- restore finishes.
+ description: Function route not found on the Frontend
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Backups are temporarily unavailable
+ '500':
+ description: Failed to delete Function route
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/backup-schedule:
+ /projects/{id}/variables:
get:
tags:
- - Database Backups
- summary: Get the automated backup schedule
+ - Variables
+ summary: List all variables for a project
description: |
- Returns the database's backup schedule. An empty list means no scheduled
- backups.
- operationId: getDatabaseBackupSchedule
+ Returns project-level environment variables used by deployed functions and frontends.
+ operationId: listVariables
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
responses:
'200':
description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBackupSchedule'
- '403':
- description: Backups are PRO-only and the owner's plan does not include them
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/PaginatedVariables'
'404':
- description: Project or database not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: |
- The database has no storage project yet, so it has no schedule. A
- database reports this while it is still provisioning.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Backups are temporarily unavailable
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- put:
+ post:
tags:
- - Database Backups
- summary: Replace the automated backup schedule
+ - Variables
+ summary: Create or update a variable
description: |
- Replaces the schedule wholesale. Send an empty `entries` list to stop
- scheduled backups.
-
- Scheduled backups do not count against the plan's backup allowance, but
- their retention is clamped to the plan's.
- operationId: updateDatabaseBackupSchedule
+ Creates a project-level environment variable and triggers asynchronous propagation
+ to deployed functions and frontends in the project's configured regions.
+ operationId: createVariable
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBackupSchedule'
+ $ref: '#/components/schemas/CreateVariableRequest'
responses:
- '200':
- description: Schedule replaced
+ '201':
+ description: Variable created
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseBackupSchedule'
+ $ref: '#/components/schemas/Variable'
'400':
- description: |
- The schedule names a recurrence that cannot fire: a weekly or
- monthly one with no `day`, or a `day` outside its frequency's range
- (1-7 for weekly, 1-28 for monthly). The response says which.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Backups are PRO-only and the owner's plan does not include them
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project or database not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '409':
- description: |
- The database is not active, or a restore is running on it — a restore
- moves the data to a new branch, and the provider keeps the schedule
- per branch.
+ description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Backups are temporarily unavailable
+ description: Private variable membership writes are disabled during rollout
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/restores:
+ /projects/{id}/databases:
get:
tags:
- - Database Backups
- summary: List a database's restores
+ - Databases
+ summary: List all databases for a project
description: |
- Returns the database's restore history, newest first, capped at the 50
- most recent. There is no pagination: a database that has been restored
- more than 50 times keeps the older records but does not return them.
- operationId: listDatabaseRestores
+ Supports two mutually exclusive pagination modes. Offset mode uses `page`
+ and `limit`. Cursor mode uses `cursor` and `limit`, supports `search`
+ (case-insensitive name match), and returns `next_cursor`/`prev_cursor`.
+ The optional `status` filter applies in both modes and is bound to the
+ cursor. Sending both `page` and `cursor` (or `page` and `search`) returns 400.
+ operationId: listDatabases
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
+ - name: status
+ in: query
+ required: false
+ schema:
+ type: string
+ enum:
+ - provisioning
+ - active
+ - restoring
+ - failed
+ - deleting
+ description: Return only the databases in this status.
responses:
'200':
description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseRestoreList'
- '403':
- description: Backups are PRO-only and the owner's plan does not include them
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project or database not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Backups are temporarily unavailable
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/PaginatedDatabases'
post:
tags:
- - Database Backups
- summary: Restore a database
+ - Databases
+ summary: Create a new serverless PostgreSQL database
description: |
- Replaces the database's data, either with a named backup or with its
- state at a point in time. This is destructive: everything written after
- that point is discarded.
-
- Asynchronous: the response is `202` with the restore `pending` and the
- database `restoring`. The database does not accept connections until the
- restore reports `completed`; its connection string is unchanged
- throughout, so nothing holding it needs updating.
-
- Restores are in place. There is no way to restore into a second
- database, and a database's branches are never restored — they keep
- serving their own data, but resetting a branch from its parent is
- refused by the storage provider for up to 24 hours afterwards.
- operationId: createDatabaseRestore
+ Creates a serverless PostgreSQL database in the project.
+ Each project can hold 1 database on Free and up to 10,000 on Pro.
+ Requests over the plan's cap return 403.
+ operationId: createDatabase
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateDatabaseRestoreRequest'
+ $ref: '#/components/schemas/CreateDatabaseRequest'
responses:
- '202':
- description: Restore accepted and in progress
+ '201':
+ description: Database created (provisioning)
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseRestore'
- '400':
- description: |
- Neither or both restore targets were named, or the requested time is
- outside the available window.
+ $ref: '#/components/schemas/Database'
+ '403':
+ description: Database limit exceeded for the project
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
+ /projects/{id}/databases/{databaseName}:
+ get:
+ tags:
+ - Databases
+ summary: Get database details
+ operationId: getDatabase
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DatabaseName'
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Database'
+ delete:
+ tags:
+ - Databases
+ summary: Delete a database
+ description: |
+ Deletes a database and the instance backing it. When the instance is
+ removed synchronously the database row is deleted and the response is
+ `204`. If the instance cannot be deleted right away, the database row
+ is retained (status `deleting`) and its teardown is handed to the
+ background reconciler, which retries the deletion and removes the row
+ once the instance is gone; in that case the response is `202`. The database row is
+ never dropped while its instance still exists, so an instance is
+ never orphaned without a record to retry from.
+ operationId: deleteDatabase
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DatabaseName'
+ responses:
+ '202':
description: |
- The owner's plan does not include backups or point-in-time restore.
- Both are PRO-only.
+ Deletion accepted and in progress. The backing instance could not be
+ removed synchronously, so the database is marked `deleting` and torn
+ down asynchronously by the reconciler.
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ type: object
+ properties:
+ status:
+ type: string
+ example: deleting
+ message:
+ type: string
+ example: database deletion in progress
+ '204':
+ description: Database deleted (backing instance removed synchronously)
'404':
- description: Project, database, or backup not found
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: |
- A restore is already in progress, the database is not active,
- another database operation is still running, or the database is
- holding as many pre-restore branches as it may.
+ A restore is running on the database. Deleting it while a worker is
+ replacing its data would race that worker, so wait for the restore
+ to finish.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Backups are temporarily unavailable
+ description: |
+ Volcano could not check whether a restore is running, and will not
+ delete a database that might be mid-restore. Retry.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/restores/{restoreId}:
+ /projects/{id}/databases/{databaseName}/branches:
get:
tags:
- - Database Backups
- summary: Get a restore
+ - Database Branches
+ summary: List a database's branches
description: |
- Returns the restore. Poll this after starting one; the database is
- connectable again once it reports `completed`.
- operationId: getDatabaseRestore
+ Returns every branch of the database, including those still provisioning
+ and those that failed, since each still holds a name.
+
+ Connection strings are omitted. Fetch a single branch to get its
+ connection string.
+ operationId: listDatabaseBranches
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/RestoreId'
responses:
'200':
description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseRestore'
- '403':
- description: Backups are PRO-only and the owner's plan does not include them
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/DatabaseBranchList'
'404':
- description: Project, database, or restore not found
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Backups are temporarily unavailable
+ description: Branching is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/reset-password:
post:
tags:
- - Databases
- summary: Reset database password
+ - Database Branches
+ summary: Create a branch of a database
description: |
- Rotates the Volcano-managed PostgreSQL password used by clients when connecting
- through pgproxy. This does not rotate or expose the internal owner password.
- The returned password and connection string are the only client credentials that
- will authenticate through pgproxy after reset.
+ Forks the database into a new branch. The branch starts as an exact copy
+ of the parent's data and diverges from there.
- Existing connections are not interrupted; new ones must use the returned
- string. Proxies pick the rotation up within a few seconds, so the previous
- password can still open new connections until then.
- operationId: resetDatabasePassword
+ Provisioning is asynchronous: the response is `202` with the branch in
+ `provisioning` and no connection string. Poll the branch until it reports
+ `active`, at which point it carries its own connection string.
+
+ Retrying a create with a name that already exists returns `409` rather
+ than a second branch, so a retried request cannot silently consume two
+ slots of the branch allowance.
+ operationId: createDatabaseBranch
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateDatabaseBranchRequest'
responses:
- '200':
- description: Password reset successful
+ '202':
+ description: Branch accepted and provisioning
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
- role_name:
- type: string
- description: Volcano-managed per-database client login (also the pgproxy routing username)
- example: volcano_client_11111111-1111-1111-1111-111111111111
- new_password:
- type: string
- description: New Volcano-managed client password. Always starts with `vpg_`.
- connection_string:
- type: string
- description: Updated pgproxy connection string using Volcano-managed credentials.
+ $ref: '#/components/schemas/DatabaseBranch'
'400':
- description: Database is not active
+ description: Invalid branch name or lifetime
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: |
+ The database has reached its branch allowance, or the owner's plan
+ does not include branching.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Database not found
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: |
- A restore is running on the database. A restore replaces the
- credentials as it finishes, so wait for it and rotate afterwards.
+ A branch of that name already exists on this database, or the
+ database cannot be branched right now because it is still
+ provisioning, being restored, failed, or being deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: |
- Volcano could not check whether a restore is running, and will not
- rotate a credential a restore might be about to replace. Retry.
+ description: Branching is temporarily unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/branches/{branchName}:
+ get:
+ tags:
+ - Database Branches
+ summary: Get a branch
+ description: |
+ Returns the branch, including its connection string once it is `active`.
+ Poll this after creating a branch to learn when it is connectable.
+ operationId: getDatabaseBranch
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
+ responses:
+ '200':
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DatabaseBranch'
+ '404':
+ description: Project, database, or branch not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Branching is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/type:
patch:
tags:
- - Databases
- summary: Update database size
+ - Database Branches
+ summary: Extend a branch's lifetime
description: |
- Change the size tier of a database. This may briefly interrupt active connections.
-
- **Available sizes:**
- - `volcano-db-xs`: Up to ~1GB RAM - Development, small apps
- - `volcano-db-s`: Up to ~4GB RAM - Production-ready, light traffic
- - `volcano-db-m`: Up to ~8GB RAM - Medium traffic applications
- - `volcano-db-l`: Up to ~16GB RAM - High traffic, larger datasets
- - `volcano-db-xl`: Up to ~32GB RAM - Heavy workloads
- - `volcano-db-2xl`: Up to ~64GB RAM - Enterprise-scale
- operationId: updateDatabaseType
+ Replaces the branch's lifetime and restarts the countdown from now, so a
+ branch you are still working on is not swept mid-session. The new
+ duration is remembered, so a later reset re-arms the same lifetime.
+ operationId: updateDatabaseBranch
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateDatabaseTypeRequest'
+ $ref: '#/components/schemas/UpdateDatabaseBranchRequest'
responses:
'200':
- description: Database type updated
+ description: Lifetime updated
content:
application/json:
schema:
- $ref: '#/components/schemas/Database'
+ $ref: '#/components/schemas/DatabaseBranch'
'400':
- description: Invalid database type
+ description: Requested lifetime is outside the allowed range
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Database not found
+ description: Project, database, or branch not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'409':
- description: |
- The database is not active — being provisioned, deleted, or
- restored. Compute can only be changed while it is active.
+ description: The branch is being deleted
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: |
- Volcano could not check whether a restore is running, and will not
- reconfigure compute a restore might be moving. Retry.
+ description: Branching is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/databases/{databaseName}/stats:
- get:
+ delete:
tags:
- - Databases
- summary: Get database consumption metrics
+ - Database Branches
+ summary: Delete a branch
description: |
- Retrieve consumption metrics including storage, compute time, and data transfer.
- Metrics are aggregated at the project level. Defaults to last 24 hours.
+ Marks the branch for teardown and returns immediately. The branch stops
+ accepting connections at once; its fork and its row are removed by a
+ background job, so a provider outage cannot leave the call hanging or the
+ branch half-deleted.
- **Note:** Advanced metrics require an upgraded plan.
- operationId: getDatabaseStats
+ Deleting a branch that is still provisioning is allowed and stops the
+ build, and repeating the call while teardown is in progress is accepted
+ again. Once the branch is gone the call returns `404`.
+ operationId: deleteDatabaseBranch
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - name: from
- in: query
- required: false
- schema:
- type: string
- format: date-time
- description: Start time in RFC3339 format (e.g., "2024-01-01T00:00:00Z"). Defaults to 24 hours ago.
- - name: to
- in: query
- required: false
- schema:
- type: string
- format: date-time
- description: End time in RFC3339 format (e.g., "2024-01-02T00:00:00Z"). Defaults to now.
- - name: granularity
- in: query
- required: false
- schema:
- type: string
- enum:
- - hourly
- - daily
- - monthly
- default: hourly
- description: Level of detail for metrics aggregation
+ - $ref: '#/components/parameters/BranchName'
responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseStats'
- '400':
- description: Invalid parameters
+ '202':
+ description: Deletion accepted and in progress
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ type: object
+ properties:
+ status:
+ type: string
+ example: deleting
+ message:
+ type: string
+ example: branch deletion in progress
+ required:
+ - status
+ - message
'404':
- description: Database not found
+ description: Project, database, or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Database metrics not available or requires upgraded plan
+ description: Branching is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /databases/{databaseName}/query/ping:
+ /projects/{id}/databases/{databaseName}/branches/{branchName}/reset:
post:
tags:
- - Database Queries
- summary: Database connectivity probe (REST API)
+ - Database Branches
+ summary: Reset a branch to its parent's current state
description: |
- Connectivity probe that runs a fixed `SELECT 1` through pgproxy, using the
- same authentication, status/bandwidth gating, and metering as the other
- `/query/*` endpoints.
+ Discards everything written on the branch and re-forks it from the
+ parent as it is now.
- Unlike those endpoints, ping takes **no request body** and performs **no
- table-name validation**, so it works on any database — including a freshly
- provisioned, empty one. It is a real committed round-trip through pgproxy,
- so a `200` means the database is reachable and queryable. Used by the
- dashboard's database connection test.
- operationId: queryDatabasePing
+ Returns immediately with the branch in `provisioning`. The rewind runs in
+ the background; poll the branch until it reports `active` before
+ connecting again.
+
+ The branch keeps its name and its connection string, so anything holding
+ that string keeps working once it is active again, and its lifetime is
+ re-armed to the duration it was created with. The branch does not serve
+ connections for the duration of the reset.
+ operationId: resetDatabaseBranch
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
responses:
- '200':
- description: Database is reachable
+ '202':
+ description: Branch reset accepted; the branch is provisioning
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - '?column?': 1
- count: 1
- '401':
- description: Not authenticated
+ $ref: '#/components/schemas/DatabaseBranch'
+ '404':
+ description: Project, database, or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '409':
+ description: |
+ The branch is not active, a reset is already in progress, the parent
+ database is being restored, or the parent was restored within the
+ last 24 hours — a reset re-forks from the parent, and the provider
+ holds a child's reset shut for that long afterwards.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database not found
+ '503':
+ description: Branching is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- /databases/{databaseName}/query/select:
+ /projects/{id}/databases/{databaseName}/branches/{branchName}/reset-password:
post:
tags:
- - Database Queries
- summary: Query database with SELECT (REST API)
+ - Database Branches
+ summary: Rotate a branch's password
description: |
- Query your database using a simple REST API - no SQL required!
-
- **Authentication:** Requires auth user access token (from signup/signin)
-
- **Row-Level Security:** Automatically enforced - you see only data you have access to
-
- **Use Cases:**
- - Query from browser/mobile apps
- - Simple data retrieval
- - Filtered searches with sorting and pagination
+ Issues a new password for the branch and invalidates the previous
+ connection string. Existing connections are not interrupted; new ones
+ must use the returned string. Proxies pick the rotation up within a few
+ seconds, so the previous password can still open new connections until
+ then.
- **Note:** For complex queries (JOINs, CTEs), use functions with direct SQL
- operationId: queryDatabaseSelect
+ The parent database's credentials are untouched.
+ operationId: resetDatabaseBranchPassword
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseSelectRequest'
+ - $ref: '#/components/parameters/BranchName'
responses:
'200':
- description: Query successful
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: uuid-123
- title: My Post
- content: Post content
- status: published
- views: 150
- created_at: '2026-01-13T10:00:00Z'
- count: 1
- '400':
- description: Invalid query
+ description: Password rotated
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ $ref: '#/components/schemas/DatabaseBranch'
+ '404':
+ description: Project, database, or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '409':
+ description: The branch is not active
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database not found
+ '503':
+ description: Branching is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- /databases/{databaseName}/query/insert:
- post:
+ /projects/{id}/databases/{databaseName}/backups:
+ get:
tags:
- - Database Queries
- summary: Insert data into database (REST API)
+ - Database Backups
+ summary: List a database's backups
description: |
- Insert new rows into your database using REST API.
-
- **Authentication:** Requires auth user access token
-
- **Auto-set user_id:** If your table has a trigger using `auth.uid()`,
- user_id will be automatically set to the authenticated user
+ Returns every backup of the database, newest first, together with the
+ window a point-in-time restore may target.
- **Security:** Row-Level Security policies are enforced
- operationId: queryDatabaseInsert
+ Both backups you took and backups the schedule produced are listed;
+ `source` tells them apart. Only manual backups count against the plan's
+ backup allowance.
+ operationId: listDatabaseBackups
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseInsertRequest'
responses:
'200':
- description: Insert successful
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: uuid-123
- title: My New Post
- content: This is the content
- status: draft
- user_id: user-uuid
- created_at: '2026-01-13T10:00:00Z'
- count: 1
- '400':
- description: Invalid request
+ $ref: '#/components/schemas/DatabaseBackupList'
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '404':
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '409':
+ description: |
+ The database has no storage project yet, so there is nothing to
+ list. A database reports this while it is still provisioning.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database not found
+ '503':
+ description: Backups are temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- /databases/{databaseName}/query/update:
post:
tags:
- - Database Queries
- summary: Update data in database (REST API)
+ - Database Backups
+ summary: Back up a database
description: |
- Update existing rows in your database using REST API.
-
- **Security:** Row-Level Security ensures you can only update data you have access to
-
- **Safety:** Requires at least one filter to prevent accidental mass updates. A
- request with no `filters` is rejected with `400` (mirrors delete). This matters
- for service-key queries, which run with full access and bypass RLS.
+ Captures the database as it is now. The backup is available immediately;
+ its `size_bytes` appears once the storage provider has costed it.
- **Note:** If RLS blocks the update, an empty result is returned (not an error)
- operationId: queryDatabaseUpdate
+ Backups are rate-limited to one per minute per database, and capped by
+ the owner's plan.
+ operationId: createDatabaseBackup
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseUpdateRequest'
+ $ref: '#/components/schemas/CreateDatabaseBackupRequest'
responses:
- '200':
- description: Update successful
+ '201':
+ description: Backup created
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: post-uuid
- title: Updated Title
- status: published
- updated_at: '2026-01-13T10:05:00Z'
- count: 1
+ $ref: '#/components/schemas/DatabaseBackup'
'400':
- description: Invalid request
+ description: Invalid backup name
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '403':
+ description: |
+ The database has reached its backup allowance, or the owner's plan
+ does not include backups, which are PRO-only.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '404':
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database not found
+ '409':
+ description: |
+ A backup of that name already exists, the database is not active, a
+ restore is running on it, or a backup was taken too recently.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- /databases/{databaseName}/query/delete:
- post:
+ '503':
+ description: Backups are temporarily unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/backups/{backupName}:
+ get:
tags:
- - Database Queries
- summary: Delete data from database (REST API)
- description: |
- Delete rows from your database using REST API.
-
- **Safety:** Requires at least one filter to prevent accidental mass deletions
-
- **Security:** Row-Level Security ensures you can only delete data you have access to
-
- **Note:** If RLS blocks the delete, an empty result is returned (not an error)
- operationId: queryDatabaseDelete
+ - Database Backups
+ summary: Get a backup
+ description: Returns one backup of the database.
+ operationId: getDatabaseBackup
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseDeleteRequest'
+ - $ref: '#/components/parameters/BackupName'
responses:
'200':
- description: Delete successful
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: post-uuid
- title: Deleted Post
- count: 1
- '400':
- description: Invalid request or missing filters
+ $ref: '#/components/schemas/DatabaseBackup'
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '404':
+ description: Project, database, or backup not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '409':
+ description: |
+ The database has no storage project yet, so it holds no backups. A
+ database reports this while it is still provisioning.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database not found
+ '503':
+ description: Backups are temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- /databases/{databaseName}/branches/{branchName}/query/ping:
- post:
+ delete:
tags:
- - Database Queries
- summary: Database connectivity probe (REST API)
+ - Database Backups
+ summary: Delete a backup
description: |
- Connectivity probe that runs a fixed `SELECT 1` through pgproxy, using the
- same authentication, status/bandwidth gating, and metering as the other
- `/query/*` endpoints.
-
- Unlike those endpoints, ping takes **no request body** and performs **no
- table-name validation**, so it works on any database — including a freshly
- provisioned, empty one. It is a real committed round-trip through pgproxy,
- so a `200` means the database is reachable and queryable. Used by the
- dashboard's database connection test.
-
- **Branch-targeted.** Runs against the named branch instead of the parent
- database, using the branch's own credentials. The branch must be `active`
- and unexpired. Nothing about this request can reach the parent's data.
- operationId: queryDatabaseBranchPing
+ Deletes the backup and frees its storage. Scheduled backups can be
+ deleted too. A backup that is already gone reports `404`, so a name
+ that never existed and a name that no longer does read the same.
+ Refused with `409` while the database is being restored.
+ operationId: deleteDatabaseBackup
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
+ - $ref: '#/components/parameters/BackupName'
responses:
'200':
- description: Database is reachable
+ description: Backup deleted
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - '?column?': 1
- count: 1
- '401':
- description: Not authenticated
+ type: object
+ properties:
+ status:
+ type: string
+ example: deleted
+ message:
+ type: string
+ example: backup deleted
+ required:
+ - status
+ - message
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '404':
+ description: Project, database, or backup not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database or branch not found
+ '409':
+ description: |
+ The database is being restored. A restore is pinned to a backup it
+ may not have restored yet, so deleting one is refused until the
+ restore finishes.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
'503':
- $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
- /databases/{databaseName}/branches/{branchName}/query/select:
- post:
+ description: Backups are temporarily unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/backup-schedule:
+ get:
tags:
- - Database Queries
- summary: Query database with SELECT (REST API)
+ - Database Backups
+ summary: Get the automated backup schedule
description: |
- Query your database using a simple REST API - no SQL required!
-
- **Authentication:** Requires auth user access token (from signup/signin)
-
- **Row-Level Security:** Automatically enforced - you see only data you have access to
-
- **Use Cases:**
- - Query from browser/mobile apps
- - Simple data retrieval
- - Filtered searches with sorting and pagination
-
- **Note:** For complex queries (JOINs, CTEs), use Lambda functions with direct SQL
-
- **Branch-targeted.** Runs against the named branch instead of the parent
- database, using the branch's own credentials. The branch must be `active`
- and unexpired. Nothing about this request can reach the parent's data.
- operationId: queryDatabaseBranchSelect
+ Returns the database's backup schedule. An empty list means no scheduled
+ backups.
+ operationId: getDatabaseBackupSchedule
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseSelectRequest'
responses:
'200':
- description: Query successful
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: uuid-123
- title: My Post
- content: Post content
- status: published
- views: 150
- created_at: '2026-01-13T10:00:00Z'
- count: 1
- '400':
- description: Invalid query
+ $ref: '#/components/schemas/DatabaseBackupSchedule'
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '404':
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '409':
+ description: |
+ The database has no storage project yet, so it has no schedule. A
+ database reports this while it is still provisioning.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database or branch not found
+ '503':
+ description: Backups are temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- '503':
- $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
- /databases/{databaseName}/branches/{branchName}/query/insert:
- post:
+ put:
tags:
- - Database Queries
- summary: Insert data into database (REST API)
+ - Database Backups
+ summary: Replace the automated backup schedule
description: |
- Insert new rows into your database using REST API.
-
- **Authentication:** Requires auth user access token
-
- **Auto-set user_id:** If your table has a trigger using `auth.uid()`,
- user_id will be automatically set to the authenticated user
-
- **Security:** Row-Level Security policies are enforced
+ Replaces the schedule wholesale. Send an empty `entries` list to stop
+ scheduled backups.
- **Branch-targeted.** Runs against the named branch instead of the parent
- database, using the branch's own credentials. The branch must be `active`
- and unexpired. Nothing about this request can reach the parent's data.
- operationId: queryDatabaseBranchInsert
+ Scheduled backups do not count against the plan's backup allowance, but
+ their retention is clamped to the plan's.
+ operationId: updateDatabaseBackupSchedule
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseInsertRequest'
+ $ref: '#/components/schemas/DatabaseBackupSchedule'
responses:
'200':
- description: Insert successful
+ description: Schedule replaced
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: uuid-123
- title: My New Post
- content: This is the content
- status: draft
- user_id: user-uuid
- created_at: '2026-01-13T10:00:00Z'
- count: 1
+ $ref: '#/components/schemas/DatabaseBackupSchedule'
'400':
- description: Invalid request
+ description: |
+ The schedule names a recurrence that cannot fire: a weekly or
+ monthly one with no `day`, or a `day` outside its frequency's range
+ (1-7 for weekly, 1-28 for monthly). The response says which.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '404':
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database or branch not found
+ '409':
+ description: |
+ The database is not active, or a restore is running on it — a restore
+ moves the data to a new branch, and the provider keeps the schedule
+ per branch.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
'503':
- $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
- /databases/{databaseName}/branches/{branchName}/query/update:
- post:
+ description: Backups are temporarily unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/restores:
+ get:
tags:
- - Database Queries
- summary: Update data in database (REST API)
+ - Database Backups
+ summary: List a database's restores
description: |
- Update existing rows in your database using REST API.
-
- **Security:** Row-Level Security ensures you can only update data you have access to
-
- **Safety:** Requires at least one filter to prevent accidental mass updates. A
- request with no `filters` is rejected with `400` (mirrors delete). This matters
- for service-key queries, which run with full access and bypass RLS.
-
- **Note:** If RLS blocks the update, an empty result is returned (not an error)
-
- **Branch-targeted.** Runs against the named branch instead of the parent
- database, using the branch's own credentials. The branch must be `active`
- and unexpired. Nothing about this request can reach the parent's data.
- operationId: queryDatabaseBranchUpdate
+ Returns the database's restore history, newest first, capped at the 50
+ most recent. There is no pagination: a database that has been restored
+ more than 50 times keeps the older records but does not return them.
+ operationId: listDatabaseRestores
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseUpdateRequest'
responses:
'200':
- description: Update successful
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: post-uuid
- title: Updated Title
- status: published
- updated_at: '2026-01-13T10:05:00Z'
- count: 1
- '400':
- description: Invalid request
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ $ref: '#/components/schemas/DatabaseRestoreList'
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '404':
+ description: Project or database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database or branch not found
+ '503':
+ description: Backups are temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
- '503':
- $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
- /databases/{databaseName}/branches/{branchName}/query/delete:
post:
tags:
- - Database Queries
- summary: Delete data from database (REST API)
+ - Database Backups
+ summary: Restore a database
description: |
- Delete rows from your database using REST API.
-
- **Safety:** Requires at least one filter to prevent accidental mass deletions
-
- **Security:** Row-Level Security ensures you can only delete data you have access to
+ Replaces the database's data, either with a named backup or with its
+ state at a point in time. This is destructive: everything written after
+ that point is discarded.
- **Note:** If RLS blocks the delete, an empty result is returned (not an error)
+ Asynchronous: the response is `202` with the restore `pending` and the
+ database `restoring`. The database does not accept connections until the
+ restore reports `completed`; its connection string is unchanged
+ throughout, so nothing holding it needs updating.
- **Branch-targeted.** Runs against the named branch instead of the parent
- database, using the branch's own credentials. The branch must be `active`
- and unexpired. Nothing about this request can reach the parent's data.
- operationId: queryDatabaseBranchDelete
+ Restores are in place. There is no way to restore into a second
+ database, and a database's branches are never restored — they keep
+ serving their own data, but resetting a branch from its parent is
+ refused by the storage provider for up to 24 hours afterwards.
+ operationId: createDatabaseRestore
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
+ - $ref: '#/components/parameters/ProjectId'
- $ref: '#/components/parameters/DatabaseName'
- - $ref: '#/components/parameters/BranchName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseDeleteRequest'
+ $ref: '#/components/schemas/CreateDatabaseRestoreRequest'
responses:
- '200':
- description: Delete successful
+ '202':
+ description: Restore accepted and in progress
content:
application/json:
schema:
- $ref: '#/components/schemas/DatabaseQueryResult'
- example:
- data:
- - id: post-uuid
- title: Deleted Post
- count: 1
+ $ref: '#/components/schemas/DatabaseRestore'
'400':
- description: Invalid request or missing filters
+ description: |
+ Neither or both restore targets were named, or the requested time is
+ outside the available window.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '403':
+ description: |
+ The owner's plan does not include backups or point-in-time restore.
+ Both are PRO-only.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '404':
+ description: Project, database, or backup not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Database or branch not found
+ '409':
+ description: |
+ A restore is already in progress, the database is not active,
+ another database operation is still running, or the database is
+ holding as many pre-restore branches as it may.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- $ref: '#/components/responses/DatabaseQueryCapExceeded'
'503':
- $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
- /databases/regions:
+ description: Backups are temporarily unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/restores/{restoreId}:
get:
tags:
- - Databases
- summary: List platform-supported regions for database provisioning
- operationId: listDatabaseRegions
+ - Database Backups
+ summary: Get a restore
description: |
- Returns the regions enabled for database provisioning in this platform environment.
- These are the same regions offered for function deployment, and the only values
- the `region` field of a database accepts.
- This is a public endpoint that doesn't require authentication.
+ Returns the restore. Poll this after starting one; the database is
+ connectable again once it reports `completed`.
+ operationId: getDatabaseRestore
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/RestoreId'
responses:
'200':
- description: List of platform-supported regions
+ description: Successful response
content:
application/json:
schema:
- type: array
- items:
- type: object
- properties:
- id:
- type: string
- example: aws-us-east-1
- description: Region identifier for API usage
- name:
- type: string
- example: US East (N. Virginia)
- description: Human-readable region location
- /databases/postgres-versions:
- get:
- tags:
- - Databases
- summary: List available PostgreSQL versions
- operationId: listPostgresVersions
- description: |
- Returns a list of supported PostgreSQL major versions for database provisioning.
- This is a public endpoint that doesn't require authentication.
- responses:
- '200':
- description: List of available PostgreSQL versions
+ $ref: '#/components/schemas/DatabaseRestore'
+ '403':
+ description: Backups are PRO-only and the owner's plan does not include them
content:
application/json:
schema:
- type: array
- items:
- type: object
- properties:
- version:
- type: string
- example: '16'
- description: PostgreSQL major version number
- name:
- type: string
- example: PostgreSQL 16
- description: Human-readable version name
- default:
- type: boolean
- description: Whether this is the default version (recommended)
- deprecated:
- type: boolean
- description: Whether this version is deprecated (approaching EOL)
- /functions/runtimes:
- get:
- tags:
- - Functions
- summary: List supported function runtimes
- operationId: listFunctionRuntimes
- security: []
- description: |
- Returns the public function runtime catalog used by CLI clients to select supported runtimes,
- language defaults, and local source packaging metadata for deployments.
- This is a public endpoint that doesn't require authentication.
- responses:
- '200':
- description: Supported function runtimes
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Project, database, or restore not found
content:
application/json:
schema:
- $ref: '#/components/schemas/FunctionRuntimesResponse'
- /functions/regions:
- get:
- tags:
- - Functions
- summary: List available regions for function deployment
- operationId: listFunctionRegions
- security: []
- description: |
- Returns the configured regions where functions can be deployed, each annotated
- with a human-readable label and country flag emoji for use in UI pickers.
- This is a public endpoint that doesn't require authentication.
- responses:
- '200':
- description: Available function deployment regions
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Backups are temporarily unavailable
content:
application/json:
schema:
- type: array
- items:
- $ref: '#/components/schemas/FunctionRegion'
- /projects/{id}/variables/{name}:
- get:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/reset-password:
+ post:
tags:
- - Variables
- summary: Get variable by name
+ - Databases
+ summary: Reset database password
description: |
- Returns a project-level environment variable used by deployed functions and frontends.
- operationId: getVariable
+ Rotates the Volcano-managed PostgreSQL password used by clients when connecting
+ through pgproxy. This does not rotate or expose the internal owner password.
+ The returned password and connection string are the only client credentials that
+ will authenticate through pgproxy after reset.
+
+ Existing connections are not interrupted; new ones must use the returned
+ string. Proxies pick the rotation up within a few seconds, so the previous
+ password can still open new connections until then.
+ operationId: resetDatabasePassword
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/VariableName'
+ - $ref: '#/components/parameters/DatabaseName'
responses:
'200':
- description: Successful response
+ description: Password reset successful
content:
application/json:
schema:
- $ref: '#/components/schemas/Variable'
+ type: object
+ properties:
+ message:
+ type: string
+ role_name:
+ type: string
+ description: Volcano-managed per-database client login (also the pgproxy routing username)
+ example: volcano_client_11111111-1111-1111-1111-111111111111
+ new_password:
+ type: string
+ description: New Volcano-managed client password. Always starts with `vpg_`.
+ connection_string:
+ type: string
+ description: Updated pgproxy connection string using Volcano-managed credentials.
+ '400':
+ description: Database is not active
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Variable not found
+ description: Database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- put:
+ '409':
+ description: |
+ A restore is running on the database. A restore replaces the
+ credentials as it finishes, so wait for it and rotate afterwards.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: |
+ Volcano could not check whether a restore is running, and will not
+ rotate a credential a restore might be about to replace. Retry.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/databases/{databaseName}/type:
+ patch:
tags:
- - Variables
- summary: Update a variable
+ - Databases
+ summary: Update database size
description: |
- Updates a project-level environment variable and triggers asynchronous propagation
- to deployed functions and frontends in the project's configured regions.
- operationId: updateVariable
+ Change the size tier of a database. This may briefly interrupt active connections.
+
+ **Available sizes:**
+ - `volcano-db-xs`: Up to ~1GB RAM - Development, small apps
+ - `volcano-db-s`: Up to ~4GB RAM - Production-ready, light traffic
+ - `volcano-db-m`: Up to ~8GB RAM - Medium traffic applications
+ - `volcano-db-l`: Up to ~16GB RAM - High traffic, larger datasets
+ - `volcano-db-xl`: Up to ~32GB RAM - Heavy workloads
+ - `volcano-db-2xl`: Up to ~64GB RAM - Enterprise-scale
+ operationId: updateDatabaseType
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/VariableName'
+ - $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateVariableRequest'
+ $ref: '#/components/schemas/UpdateDatabaseTypeRequest'
responses:
'200':
- description: Variable updated
+ description: Database type updated
content:
application/json:
schema:
- $ref: '#/components/schemas/Variable'
+ $ref: '#/components/schemas/Database'
+ '400':
+ description: Invalid database type
'404':
- description: Variable not found
+ description: Database not found
+ '409':
+ description: |
+ The database is not active — being provisioned, deleted, or
+ restored. Compute can only be changed while it is active.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
- description: Private variable membership writes are disabled during rollout
+ description: |
+ Volcano could not check whether a restore is running, and will not
+ reconfigure compute a restore might be moving. Retry.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ /projects/{id}/databases/{databaseName}/stats:
+ get:
tags:
- - Variables
- summary: Delete a variable
+ - Databases
+ summary: Get database consumption metrics
description: |
- Deletes a project-level environment variable and triggers asynchronous propagation
- of the removal to deployed functions and frontends in the project's configured regions.
- operationId: deleteVariable
+ Retrieve consumption metrics including storage, compute time, and data transfer.
+ Metrics are aggregated at the project level. Defaults to last 24 hours.
+
+ **Note:** Advanced metrics require an upgraded plan.
+ operationId: getDatabaseStats
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/VariableName'
+ - $ref: '#/components/parameters/DatabaseName'
+ - name: from
+ in: query
+ required: false
+ schema:
+ type: string
+ format: date-time
+ description: Start time in RFC3339 format (e.g., "2024-01-01T00:00:00Z"). Defaults to 24 hours ago.
+ - name: to
+ in: query
+ required: false
+ schema:
+ type: string
+ format: date-time
+ description: End time in RFC3339 format (e.g., "2024-01-02T00:00:00Z"). Defaults to now.
+ - name: granularity
+ in: query
+ required: false
+ schema:
+ type: string
+ enum:
+ - hourly
+ - daily
+ - monthly
+ default: hourly
+ description: Level of detail for metrics aggregation
responses:
- '204':
- description: Variable deleted
- '404':
- description: Variable not found
+ '200':
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- /auth/password-policy:
- get:
- tags:
- - Authentication
- summary: Get the effective password policy
- description: |
- Returns the backend-enforced password bounds and compromised-password
- screening status for the project identified by the anon key. A valid
- anon key is required, but no route-specific auth permission is needed.
- operationId: authGetPasswordPolicy
- security:
- - AnonKey: []
- responses:
- '200':
- description: Effective password policy
+ $ref: '#/components/schemas/DatabaseStats'
+ '400':
+ description: Invalid parameters
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthPasswordPolicy'
- '401':
- description: Invalid, missing, or revoked anon key
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project or auth configuration not found
+ '503':
+ description: Database metrics not available or requires upgraded plan
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/signup:
+ /databases/{databaseName}/query/ping:
post:
tags:
- - Authentication
- summary: Sign up a new auth user
+ - Database Queries
+ summary: Database connectivity probe (REST API)
description: |
- Create a new end-user account. The project is determined from the anon key.
- Requires project-specific anon key in Authorization header.
-
- **Session-less**: signup never issues a session. On success it returns a
- uniform acknowledgement (`AuthSignupResponse`) with no tokens; the client
- obtains a session with a subsequent `POST /auth/signin`. If email confirmation
- is enabled for the project, a confirmation email is sent and
- `confirmation_required` is `true`.
+ Connectivity probe that runs a fixed `SELECT 1` through pgproxy, using the
+ same authentication, status/bandwidth gating, and metering as the other
+ `/query/*` endpoints.
- **Anti-enumeration**: a signup for an already-registered email returns the
- exact same `201` response as a fresh signup — it never returns `409` — so the
- response cannot be used to discover which emails are registered.
- operationId: authSignup
+ Unlike those endpoints, ping takes **no request body** and performs **no
+ table-name validation**, so it works on any database — including a freshly
+ provisioned, empty one. It is a real committed round-trip through pgproxy,
+ so a `200` means the database is reachable and queryable. Used by the
+ dashboard's database connection test.
+ operationId: queryDatabasePing
security:
- - AnonKey: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - email
- - password
- properties:
- email:
- type: string
- format: email
- password:
- type: string
- description: |
- Password validated after NFC normalization against the
- policy returned by GET /auth/password-policy.
- user_metadata:
- type: object
- additionalProperties: true
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
responses:
- '201':
- description: |
- Signup acknowledged (session-less). Returned identically for a new
- account and for an already-registered email (anti-enumeration).
+ '200':
+ description: Database is reachable
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthSignupResponse'
- '400':
- description: Invalid input (bad email/password format)
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - '?column?': 1
+ count: 1
'401':
- description: |
- Unauthorized - Invalid, tampered, revoked, or wrong-project anon key
+ description: Not authenticated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: |
- Forbidden - Signups disabled, anon key lacks signup permission, or the
- email domain is not in `allowed_email_domains`. The internal
- `anonymous.volcano.internal` domain is reserved for anonymous
- accounts and is refused whatever the project allows.
- '429':
- description: Rate limit exceeded
- '503':
- description: Compromised-password screening is temporarily unavailable
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/signin:
+ '404':
+ description: Database not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ /databases/{databaseName}/query/select:
post:
tags:
- - Authentication
- summary: Sign in an auth user
+ - Database Queries
+ summary: Query database with SELECT (REST API)
description: |
- Authenticate with email and password. Requires an anon key.
+ Query your database using a simple REST API - no SQL required!
- Set `session_mode` to `cookie` to request HttpOnly refresh-token
- storage. Cookie mode is honored only for an exact, credentialed CORS
- origin on the same schemeful site as this API. Otherwise the response
- retains the refresh token in its body.
- operationId: authSignin
+ **Authentication:** Requires auth user access token (from signup/signin)
+
+ **Row-Level Security:** Automatically enforced - you see only data you have access to
+
+ **Use Cases:**
+ - Query from browser/mobile apps
+ - Simple data retrieval
+ - Filtered searches with sorting and pagination
+
+ **Note:** For complex queries (JOINs, CTEs), use functions with direct SQL
+ operationId: queryDatabaseSelect
security:
- - AnonKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - email
- - password
- properties:
- email:
- type: string
- password:
- type: string
- session_mode:
- type: string
- enum:
- - cookie
+ $ref: '#/components/schemas/DatabaseSelectRequest'
responses:
'200':
- description: Signin successful
+ description: Query successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthTokenResponse'
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: uuid-123
+ title: My Post
+ content: Post content
+ status: published
+ views: 150
+ created_at: '2026-01-13T10:00:00Z'
+ count: 1
'400':
- description: Invalid input (missing email/password)
+ description: Invalid query
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'401':
- description: |
- Unauthorized - Invalid credentials, invalid/tampered/revoked anon key,
- or account banned/deleted
+ description: Not authenticated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: |
- Forbidden - Anon key lacks signin permission, or the email domain is
- not in `allowed_email_domains` while `allowed_email_domains_mode` is
- `signup_and_signin`. The domain is taken from the account's canonical
- email (its primary identity), which is not necessarily the address in
- the request.
+ description: Access denied
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Database not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'429':
- description: Rate limit exceeded
- /auth/refresh:
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ /databases/{databaseName}/query/insert:
post:
tags:
- - Authentication
- summary: Refresh access token
+ - Database Queries
+ summary: Insert data into database (REST API)
description: |
- Get a new access token using a refresh token. Requires an anon key.
+ Insert new rows into your database using REST API.
- Send `refresh_token` in the body for the default flow. An eligible
- cookie-mode browser request may instead send `session_mode: cookie`
- with an empty token or omit the request body; the API reads and resets
- the project's HttpOnly cookie and omits `refresh_token` from the
- response.
- operationId: authRefresh
+ **Authentication:** Requires auth user access token
+
+ **Auto-set user_id:** If your table has a trigger using `auth.uid()`,
+ user_id will be automatically set to the authenticated user
+
+ **Security:** Row-Level Security policies are enforced
+ operationId: queryDatabaseInsert
security:
- - AnonKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
requestBody:
- required: false
+ required: true
content:
application/json:
schema:
- type: object
- properties:
- refresh_token:
- type: string
- session_mode:
- type: string
- enum:
- - cookie
+ $ref: '#/components/schemas/DatabaseInsertRequest'
responses:
'200':
- description: Token refreshed
+ description: Insert successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthTokenResponse'
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: uuid-123
+ title: My New Post
+ content: This is the content
+ status: draft
+ user_id: user-uuid
+ created_at: '2026-01-13T10:00:00Z'
+ count: 1
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'401':
- description: Invalid or expired refresh token
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: |
- The account's email domain is not in `allowed_email_domains` while
- `allowed_email_domains_mode` is `signup_and_signin`, so the session
- cannot be extended
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- description: Rate limit exceeded
+ '404':
+ description: Database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/logout:
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ /databases/{databaseName}/query/update:
post:
tags:
- - Authentication
- summary: Logout (revoke refresh token)
+ - Database Queries
+ summary: Update data in database (REST API)
description: |
- Invalidate a refresh token. Requires an anon key.
+ Update existing rows in your database using REST API.
- Send `refresh_token` for the default flow. An eligible cookie-mode
- browser request may instead send `session_mode: cookie` with an empty
- token; logout remains idempotent when the cookie is missing or expired.
- operationId: authLogout
- security:
- - AnonKey: []
- requestBody:
- required: false
- content:
- application/json:
- schema:
- type: object
- properties:
- refresh_token:
- type: string
- session_mode:
- type: string
- enum:
- - cookie
- responses:
- '204':
- description: Logged out successfully
- /auth/forgot-password:
- post:
- tags:
- - Authentication
- summary: Request password reset
- description: |
- Generates recovery token and stores it (email sending pending).
- Returns generic message to prevent email enumeration.
- Project is identified via the anon key.
- operationId: authForgotPassword
+ **Security:** Row-Level Security ensures you can only update data you have access to
+
+ **Safety:** Requires at least one filter to prevent accidental mass updates. A
+ request with no `filters` is rejected with `400` (mirrors delete). This matters
+ for service-key queries, which run with full access and bypass RLS.
+
+ **Note:** If RLS blocks the update, an empty result is returned (not an error)
+ operationId: queryDatabaseUpdate
security:
- - AnonKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - email
- properties:
- email:
- type: string
- format: email
+ $ref: '#/components/schemas/DatabaseUpdateRequest'
responses:
'200':
- description: Generic success message (doesn't reveal if email exists)
+ description: Update successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
- example: If the email exists, a password reset link has been sent
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: post-uuid
+ title: Updated Title
+ status: published
+ updated_at: '2026-01-13T10:05:00Z'
+ count: 1
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Not authenticated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: Password reset is disabled for this project
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- description: Rate limit exceeded (10 requests per hour per IP)
+ '404':
+ description: Database not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/reset-password:
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ /databases/{databaseName}/query/delete:
post:
tags:
- - Authentication
- summary: Reset password with recovery token
+ - Database Queries
+ summary: Delete data from database (REST API)
description: |
- Reset password using recovery token from forgot-password.
- Revokes all existing sessions for security.
- operationId: authResetPassword
+ Delete rows from your database using REST API.
+
+ **Safety:** Requires at least one filter to prevent accidental mass deletions
+
+ **Security:** Row-Level Security ensures you can only delete data you have access to
+
+ **Note:** If RLS blocks the delete, an empty result is returned (not an error)
+ operationId: queryDatabaseDelete
security:
- - AnonKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - token
- - new_password
- properties:
- token:
- type: string
- description: Recovery token from forgot-password
- new_password:
- type: string
- description: |
- Password validated after NFC normalization against the
- policy returned by GET /auth/password-policy.
+ $ref: '#/components/schemas/DatabaseDeleteRequest'
responses:
'200':
- description: Password reset successful
+ description: Delete successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: post-uuid
+ title: Deleted Post
+ count: 1
'400':
- description: Password doesn't meet requirements
+ description: Invalid request or missing filters
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'401':
- description: Invalid or expired token
- '503':
- description: Compromised-password screening is temporarily unavailable
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/confirm:
- post:
- tags:
- - Authentication
- summary: Confirm email address
- description: |
- Confirm email address using token sent via email.
- Required if require_email_confirmation is enabled.
- operationId: authConfirmEmail
- security:
- - AnonKey: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - token
- properties:
- token:
- type: string
- description: Confirmation token from email
- responses:
- '200':
- description: Email confirmed or already confirmed
+ '403':
+ description: Access denied
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
- enum:
- - Email confirmed successfully
- - Email already confirmed
- examples:
- confirmed:
- summary: Fresh confirmation
- value:
- message: Email confirmed successfully
- alreadyConfirmed:
- summary: Token belongs to already-confirmed user
- value:
- message: Email already confirmed
- '400':
- description: Missing confirmation token in request body
- '401':
- description: Invalid or expired token
- /auth/resend-confirmation:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Database not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ /databases/{databaseName}/branches/{branchName}/query/ping:
post:
tags:
- - Authentication
- summary: Resend confirmation email
+ - Database Queries
+ summary: Database connectivity probe (REST API)
description: |
- Resend email confirmation link.
- Returns generic message to prevent email enumeration.
- No email is sent when the account does not exist or is already confirmed.
- If the account exists and is unconfirmed, a new token is generated and
- any previous confirmation token is invalidated.
- operationId: authResendConfirmation
+ Connectivity probe that runs a fixed `SELECT 1` through pgproxy, using the
+ same authentication, status/bandwidth gating, and metering as the other
+ `/query/*` endpoints.
+
+ Unlike those endpoints, ping takes **no request body** and performs **no
+ table-name validation**, so it works on any database — including a freshly
+ provisioned, empty one. It is a real committed round-trip through pgproxy,
+ so a `200` means the database is reachable and queryable. Used by the
+ dashboard's database connection test.
+
+ **Branch-targeted.** Runs against the named branch instead of the parent
+ database, using the branch's own credentials. The branch must be `active`
+ and unexpired. Nothing about this request can reach the parent's data.
+ operationId: queryDatabaseBranchPing
security:
- - AnonKey: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - email
- properties:
- email:
- type: string
- format: email
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
responses:
'200':
- description: Generic success message
+ description: Database is reachable
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
- '429':
- description: Rate limit exceeded
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - '?column?': 1
+ count: 1
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/signup-anonymous:
- post:
- tags:
- - Authentication
- summary: Create anonymous user
- description: |
- Create guest user without email/password.
-
- User metadata (like display_name) can be included and will appear in realtime presence events.
- Requires enable_anonymous_signins to be true.
- operationId: authSignupAnonymous
- security:
- - AnonKey: []
- requestBody:
- description: Optional user metadata
- content:
- application/json:
- schema:
- type: object
- properties:
- user_metadata:
- type: object
- additionalProperties: true
- description: Custom user metadata (e.g., display_name, avatar_url)
- example:
- display_name: Alice
- avatar_url: https://example.com/alice.jpg
- responses:
- '201':
- description: Anonymous user created
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AuthTokenResponse'
'403':
- description: Anonymous signins disabled
- /auth/user/convert-anonymous:
- post:
- tags:
- - Authentication
- summary: Convert anonymous user to authenticated
- description: |
- Add email and password to anonymous user.
- Requires auth user access token.
- If require_email_confirmation is enabled for the project, the converted
- user remains unconfirmed until /auth/confirm succeeds. When email
- sending is enabled, a confirmation email is sent during conversion.
- operationId: authConvertAnonymous
- security:
- - AuthUserAccessToken: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - email
- - password
- properties:
- email:
- type: string
- format: email
- password:
- type: string
- description: |
- Password validated after NFC normalization against the
- policy returned by GET /auth/password-policy.
- user_metadata:
- type: object
- additionalProperties: true
- responses:
- '200':
- description: User converted successfully
+ description: Access denied
content:
application/json:
schema:
- type: object
- properties:
- user:
- $ref: '#/components/schemas/AuthUser'
- '400':
- description: Not an anonymous user
- '403':
- description: |
- The chosen email domain is not in `allowed_email_domains`
- '409':
- description: Email already in use
- '503':
- description: Compromised-password screening is temporarily unavailable
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Database or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user/change-email:
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ '503':
+ $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
+ /databases/{databaseName}/branches/{branchName}/query/select:
post:
tags:
- - Authentication
- summary: Request email change
+ - Database Queries
+ summary: Query database with SELECT (REST API)
description: |
- Request to change user's email address.
- Sends confirmation token to new email address.
- operationId: authRequestEmailChange
+ Query your database using a simple REST API - no SQL required!
+
+ **Authentication:** Requires auth user access token (from signup/signin)
+
+ **Row-Level Security:** Automatically enforced - you see only data you have access to
+
+ **Use Cases:**
+ - Query from browser/mobile apps
+ - Simple data retrieval
+ - Filtered searches with sorting and pagination
+
+ **Note:** For complex queries (JOINs, CTEs), use Lambda functions with direct SQL
+
+ **Branch-targeted.** Runs against the named branch instead of the parent
+ database, using the branch's own credentials. The branch must be `active`
+ and unexpired. Nothing about this request can reach the parent's data.
+ operationId: queryDatabaseBranchSelect
security:
- AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - new_email
- properties:
- new_email:
- type: string
- format: email
+ $ref: '#/components/schemas/DatabaseSelectRequest'
responses:
'200':
- description: Confirmation email sent
+ description: Query successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
- new_email:
- type: string
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: uuid-123
+ title: My Post
+ content: Post content
+ status: published
+ views: 150
+ created_at: '2026-01-13T10:00:00Z'
+ count: 1
'400':
- description: Invalid email format or same as current email
+ description: Invalid query
content:
application/json:
schema:
@@ -7686,58 +7853,76 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: |
- The requested email domain is not in `allowed_email_domains`
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: Email already in use
+ '404':
+ description: Database or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
- description: Rate limit exceeded (10 requests per hour per IP)
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /auth/user/confirm-email-change:
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ '503':
+ $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
+ /databases/{databaseName}/branches/{branchName}/query/insert:
post:
tags:
- - Authentication
- summary: Confirm email change
- description: Confirm email change with token sent to new address
- operationId: authConfirmEmailChange
+ - Database Queries
+ summary: Insert data into database (REST API)
+ description: |
+ Insert new rows into your database using REST API.
+
+ **Authentication:** Requires auth user access token
+
+ **Auto-set user_id:** If your table has a trigger using `auth.uid()`,
+ user_id will be automatically set to the authenticated user
+
+ **Security:** Row-Level Security policies are enforced
+
+ **Branch-targeted.** Runs against the named branch instead of the parent
+ database, using the branch's own credentials. The branch must be `active`
+ and unexpired. Nothing about this request can reach the parent's data.
+ operationId: queryDatabaseBranchInsert
security:
- AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - email_change_token
- properties:
- email_change_token:
- type: string
+ $ref: '#/components/schemas/DatabaseInsertRequest'
responses:
'200':
- description: Email changed successfully
+ description: Insert successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- type: object
- properties:
- message:
- type: string
- user:
- $ref: '#/components/schemas/AuthUser'
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: uuid-123
+ title: My New Post
+ content: This is the content
+ status: draft
+ user_id: user-uuid
+ created_at: '2026-01-13T10:00:00Z'
+ count: 1
'400':
- description: Invalid or expired token, or no pending email change
+ description: Invalid request
content:
application/json:
schema:
@@ -7749,181 +7934,75 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: |
- The pending email domain is no longer in `allowed_email_domains`.
- Re-checked here because the allowlist can narrow between the request
- and the confirmation.
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: Email is now in use by another user
+ '404':
+ description: Database or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user/cancel-email-change:
- delete:
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ '503':
+ $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
+ /databases/{databaseName}/branches/{branchName}/query/update:
+ post:
tags:
- - Authentication
- summary: Cancel pending email change
- operationId: authCancelEmailChange
- security:
- - AuthUserAccessToken: []
- responses:
- '200':
- description: Email change cancelled
- content:
- application/json:
- schema:
- type: object
- properties:
- message:
- type: string
- '401':
- description: Not authenticated
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /auth/user/sessions:
- get:
- tags:
- - Authentication
- summary: Get current user's sessions
+ - Database Queries
+ summary: Update data in database (REST API)
description: |
- Returns paginated sessions for the currently authenticated user.
- Each session includes device info, IP addresses, and activity timestamps.
- The current session is marked with `is_current: true`.
+ Update existing rows in your database using REST API.
- **Ordering and pagination.** Without `sort`, results are ordered by most
- recent activity and paged with `page`/`limit`, returning the
- `sessions`/`total`/`page`/`limit`/`total_pages` body below. This is the
- legacy default and is preserved for existing clients.
+ **Security:** Row-Level Security ensures you can only update data you have access to
- Send `sort=created_at` to opt into the standard list contract: results are
- ordered by session start (newest first) and may be paged either with
- `page`/`limit` or by cursor with `cursor`/`ending_before` plus a bounded
- `offset` past the cursor anchor. Cursor responses use the shared
- `data` envelope with `next_cursor`/`prev_cursor`.
+ **Safety:** Requires at least one filter to prevent accidental mass updates. A
+ request with no `filters` is rejected with `400` (mirrors delete). This matters
+ for service-key queries, which run with full access and bypass RLS.
- Unlike other list endpoints, sending `limit` without `page` does **not**
- select cursor mode here; `sort=created_at` is the only opt-in. Cursor
- pagination is rejected with 400 for the activity order, because
- `last_activity_at` changes whenever a session refreshes its token: a row
- that crosses the cursor anchor between two requests would be skipped and
- never shown. The `status=expired` filter is also offset-only because a
- session can expire above the cursor anchor during a walk. Sending that
- filter in cursor mode, `page` with `cursor` or `ending_before`, or both
- cursor directions returns 400.
- operationId: authGetMySessions
+ **Note:** If RLS blocks the update, an empty result is returned (not an error)
+
+ **Branch-targeted.** Runs against the named branch instead of the parent
+ database, using the branch's own credentials. The branch must be `active`
+ and unexpired. Nothing about this request can reach the parent's data.
+ operationId: queryDatabaseBranchUpdate
security:
- AuthUserAccessToken: []
parameters:
- - name: page
- in: query
- description: Page number (1-indexed)
- schema:
- type: integer
- minimum: 1
- default: 1
- - name: limit
- in: query
- description: Number of sessions per page (max 100)
- schema:
- type: integer
- minimum: 1
- maximum: 100
- default: 20
- - name: sort
- in: query
- description: |
- Sort key. `last_activity` (default) orders by most recent activity and
- supports offset pagination only. `created_at` orders by session start
- and supports both offset and cursor pagination.
- schema:
- type: string
- enum:
- - last_activity
- - created_at
- default: last_activity
- - name: status
- in: query
- description: |
- Filter by whether the session can still be refreshed. Omit for every
- stored session, including expired ones. `expired` is not supported
- with cursor pagination.
- schema:
- type: string
- enum:
- - active
- - expired
- - name: cursor
- in: query
- description: |
- Opaque keyset cursor from a previous response's `next_cursor`. Requires
- `sort=created_at`; mutually exclusive with `page` and `ending_before`.
- schema:
- type: string
- - name: ending_before
- in: query
- description: |
- Opaque keyset cursor from a previous response's `prev_cursor`, paging
- backward. Requires `sort=created_at`; mutually exclusive with `page`
- and `cursor`.
- schema:
- type: string
- - name: offset
- in: query
- description: |
- Bounded number of rows to skip past the cursor anchor (the hybrid
- jump, maximum 100000). Ignored unless `cursor` or `ending_before` is
- supplied.
- schema:
- type: integer
- minimum: 0
- maximum: 100000
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DatabaseUpdateRequest'
responses:
'200':
- description: Paginated list of user sessions
+ description: Update successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
content:
application/json:
schema:
- type: object
- properties:
- sessions:
- type: array
- items:
- $ref: '#/components/schemas/AuthSession'
- total:
- type: integer
- description: Total number of sessions
- page:
- type: integer
- description: Current page number
- limit:
- type: integer
- description: Number of sessions per page
- total_pages:
- type: integer
- description: Total number of pages
- data:
- type: array
- description: Sessions for this page (cursor pagination only)
- items:
- $ref: '#/components/schemas/AuthSession'
- has_more:
- type: boolean
- description: Whether a further page exists (cursor pagination only)
- next_cursor:
- type: string
- description: Opaque cursor for the next page (cursor pagination only)
- prev_cursor:
- type: string
- description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: post-uuid
+ title: Updated Title
+ status: published
+ updated_at: '2026-01-13T10:05:00Z'
+ count: 1
'400':
- description: Invalid or conflicting pagination parameters
+ description: Invalid request
content:
application/json:
schema:
@@ -7934,1317 +8013,1456 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
- tags:
- - Authentication
- summary: Sign out from all other devices
- description: |
- Deletes all sessions except the current one.
- Use this to log out from all other devices while keeping the current session active.
- operationId: authDeleteAllMySessions
- security:
- - AuthUserAccessToken: []
- responses:
- '204':
- description: All other sessions deleted
- '401':
- description: Not authenticated
+ '403':
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user/sessions/{sessionId}:
- delete:
+ '404':
+ description: Database or branch not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ '503':
+ $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
+ /databases/{databaseName}/branches/{branchName}/query/delete:
+ post:
tags:
- - Authentication
- summary: Sign out from specific device
+ - Database Queries
+ summary: Delete data from database (REST API)
description: |
- Deletes a specific session, logging out that device.
- You can get session IDs from the list sessions endpoint.
- operationId: authDeleteMySession
+ Delete rows from your database using REST API.
+
+ **Safety:** Requires at least one filter to prevent accidental mass deletions
+
+ **Security:** Row-Level Security ensures you can only delete data you have access to
+
+ **Note:** If RLS blocks the delete, an empty result is returned (not an error)
+
+ **Branch-targeted.** Runs against the named branch instead of the parent
+ database, using the branch's own credentials. The branch must be `active`
+ and unexpired. Nothing about this request can reach the parent's data.
+ operationId: queryDatabaseBranchDelete
security:
- AuthUserAccessToken: []
parameters:
- - name: sessionId
- in: path
- required: true
- description: The session ID to delete
- schema:
- type: string
- format: uuid
+ - $ref: '#/components/parameters/DatabaseName'
+ - $ref: '#/components/parameters/BranchName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DatabaseDeleteRequest'
responses:
- '204':
- description: Session deleted
+ '200':
+ description: Delete successful
+ headers:
+ X-Volcano-Proxy-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyMs'
+ X-Volcano-Proxy-Handler-Ms:
+ $ref: '#/components/headers/DatabaseQueryProxyHandlerMs'
+ X-Volcano-Compute-Ms:
+ $ref: '#/components/headers/DatabaseQueryComputeMs'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DatabaseQueryResult'
+ example:
+ data:
+ - id: post-uuid
+ title: Deleted Post
+ count: 1
+ '400':
+ description: Invalid request or missing filters
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'401':
description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ '403':
+ description: Access denied
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Session not found
+ description: Database or branch not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user:
- get:
- tags:
- - Authentication
- summary: Get current user profile
- description: Returns authenticated user's profile. Requires access token.
- operationId: authGetUser
- security:
- - AuthUserAccessToken: []
+ '429':
+ $ref: '#/components/responses/DatabaseQueryCapExceeded'
+ '503':
+ $ref: '#/components/responses/DatabaseBranchQueryUnavailable'
+ /databases/regions:
+ get:
+ tags:
+ - Databases
+ summary: List platform-supported regions for database provisioning
+ operationId: listDatabaseRegions
+ description: |
+ Returns the regions enabled for database provisioning in this platform environment.
+ These are the same regions offered for function deployment, and the only values
+ the `region` field of a database accepts.
+ This is a public endpoint that doesn't require authentication.
responses:
'200':
- description: User profile
- content:
- application/json:
- schema:
- type: object
- properties:
- user:
- $ref: '#/components/schemas/AuthUser'
- '401':
- description: Not authenticated - access token missing or invalid
+ description: List of platform-supported regions
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- put:
- tags:
- - Authentication
- summary: Update user profile
- description: Update password or metadata. Requires access token.
- operationId: authUpdateUser
- security:
- - AuthUserAccessToken: []
- requestBody:
- content:
- application/json:
- schema:
- type: object
- properties:
- password:
- type: string
- description: |
- Password validated after NFC normalization against the
- policy returned by GET /auth/password-policy.
- user_metadata:
+ type: array
+ items:
type: object
- additionalProperties: true
- description: |
- Metadata keys to merge into the current user metadata.
- Omitted keys remain unchanged; set a key to null to remove it.
- Merging is shallow; nested objects replace the stored value for that top-level key.
+ properties:
+ id:
+ type: string
+ example: aws-us-east-1
+ description: Region identifier for API usage
+ name:
+ type: string
+ example: US East (N. Virginia)
+ description: Human-readable region location
+ /databases/postgres-versions:
+ get:
+ tags:
+ - Databases
+ summary: List available PostgreSQL versions
+ operationId: listPostgresVersions
+ description: |
+ Returns a list of supported PostgreSQL major versions for database provisioning.
+ This is a public endpoint that doesn't require authentication.
responses:
'200':
- description: Profile updated
- content:
- application/json:
- schema:
- type: object
- properties:
- user:
- $ref: '#/components/schemas/AuthUser'
- '400':
- description: Bad request - invalid password or metadata format
+ description: List of available PostgreSQL versions
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated - access token missing or invalid
+ type: array
+ items:
+ type: object
+ properties:
+ version:
+ type: string
+ example: '16'
+ description: PostgreSQL major version number
+ name:
+ type: string
+ example: PostgreSQL 16
+ description: Human-readable version name
+ default:
+ type: boolean
+ description: Whether this is the default version (recommended)
+ deprecated:
+ type: boolean
+ description: Whether this version is deprecated (approaching EOL)
+ /functions/runtimes:
+ get:
+ tags:
+ - Functions
+ summary: List supported function runtimes
+ operationId: listFunctionRuntimes
+ security: []
+ description: |
+ Returns the public function runtime catalog used by CLI clients to select supported runtimes,
+ language defaults, and local source packaging metadata for deployments.
+ This is a public endpoint that doesn't require authentication.
+ responses:
+ '200':
+ description: Supported function runtimes
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Compromised-password screening is temporarily unavailable
+ $ref: '#/components/schemas/FunctionRuntimesResponse'
+ /functions/regions:
+ get:
+ tags:
+ - Functions
+ summary: List available regions for function deployment
+ operationId: listFunctionRegions
+ security: []
+ description: |
+ Returns the configured regions where functions can be deployed, each annotated
+ with a human-readable label and country flag emoji for use in UI pickers.
+ This is a public endpoint that doesn't require authentication.
+ responses:
+ '200':
+ description: Available function deployment regions
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- /auth/user/identities:
+ type: array
+ items:
+ $ref: '#/components/schemas/FunctionRegion'
+ /projects/{id}/variables/{name}:
get:
tags:
- - Authentication
- summary: List the current user's identities
+ - Variables
+ summary: Get variable by name
description: |
- Returns every real email identity the account owns. An account can own
- multiple identities (for example a password identity plus one or more
- OAuth identities on different emails). Anonymous accounts have no real
- identity and return an empty list.
- operationId: authListIdentities
+ Returns a project-level environment variable used by deployed functions and frontends.
+ operationId: getVariable
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/VariableName'
responses:
'200':
- description: List of identities
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthIdentitiesResponse'
- '401':
- description: Not authenticated
+ $ref: '#/components/schemas/Variable'
+ '404':
+ description: Variable not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user/identities/{identityId}:
- delete:
+ put:
tags:
- - Authentication
- summary: Unlink an identity from the current user
+ - Variables
+ summary: Update a variable
description: |
- Removes a non-primary identity and its attached sign-in methods. Refused
- when the identity is the account's primary, its only identity, or when
- removing it would leave the account with no way to sign in.
- operationId: authUnlinkIdentity
+ Updates a project-level environment variable and triggers asynchronous propagation
+ to deployed functions and frontends in the project's configured regions.
+ operationId: updateVariable
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - name: identityId
- in: path
- required: true
- description: The identity ID to unlink
- schema:
- type: string
- format: uuid
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/VariableName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateVariableRequest'
responses:
- '204':
- description: Identity unlinked
- '400':
- description: Identity cannot be unlinked (primary, last, or would remove last sign-in method), or the identity id is malformed
+ '200':
+ description: Variable updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Variable'
+ '404':
+ description: Variable not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
+ '503':
+ description: Private variable membership writes are disabled during rollout
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+ delete:
+ tags:
+ - Variables
+ summary: Delete a variable
+ description: |
+ Deletes a project-level environment variable and triggers asynchronous propagation
+ of the removal to deployed functions and frontends in the project's configured regions.
+ operationId: deleteVariable
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/VariableName'
+ responses:
+ '204':
+ description: Variable deleted
'404':
- description: Identity not found
+ description: Variable not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user/methods:
+ /auth/password-policy:
get:
tags:
- Authentication
- summary: List the current user's sign-in methods
+ summary: Get the effective password policy
description: |
- Returns a flat list of every sign-in method the account owns (password,
- each OAuth provider, and any active anonymous method), with the primary
- method flagged. Password stubs and converted anonymous methods are excluded.
- operationId: authListMethods
+ Returns the backend-enforced password bounds and compromised-password
+ screening status for the project identified by the anon key. A valid
+ anon key is required, but no route-specific auth permission is needed.
+ operationId: authGetPasswordPolicy
security:
- - AuthUserAccessToken: []
+ - AnonKey: []
responses:
'200':
- description: List of sign-in methods
+ description: Effective password policy
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthMethodsResponse'
+ $ref: '#/components/schemas/AuthPasswordPolicy'
'401':
- description: Not authenticated
+ description: Invalid, missing, or revoked anon key
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/user/methods/{methodId}/promote:
+ '404':
+ description: Project or auth configuration not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /auth/signup:
post:
tags:
- Authentication
- summary: Set a method as the account's primary
+ summary: Sign up a new auth user
description: |
- Promotes the given method to the account's primary sign-in method. The
- account's canonical email is re-derived from the promoted method's identity.
- Refused for password stubs and converted anonymous methods, and for an
- identity whose domain is outside the project's `allowed_email_domains`.
- operationId: authPromoteMethod
+ Create a new end-user account. The project is determined from the anon key.
+ Requires project-specific anon key in Authorization header.
+
+ **Session-less**: signup never issues a session. On success it returns a
+ uniform acknowledgement (`AuthSignupResponse`) with no tokens; the client
+ obtains a session with a subsequent `POST /auth/signin`. If email confirmation
+ is enabled for the project, a confirmation email is sent and
+ `confirmation_required` is `true`.
+
+ **Anti-enumeration**: a signup for an already-registered email returns the
+ exact same `201` response as a fresh signup — it never returns `409` — so the
+ response cannot be used to discover which emails are registered.
+ operationId: authSignup
security:
- - AuthUserAccessToken: []
- parameters:
- - name: methodId
- in: path
- required: true
- description: The method ID to promote
- schema:
- type: string
- format: uuid
+ - AnonKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - email
+ - password
+ properties:
+ email:
+ type: string
+ format: email
+ password:
+ type: string
+ description: |
+ Password validated after NFC normalization against the
+ policy returned by GET /auth/password-policy.
+ user_metadata:
+ type: object
+ additionalProperties: true
responses:
- '200':
- description: The promoted method
+ '201':
+ description: |
+ Signup acknowledged (session-less). Returned identically for a new
+ account and for an already-registered email (anti-enumeration).
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthMethodSummary'
+ $ref: '#/components/schemas/AuthSignupResponse'
'400':
- description: This method cannot be set as primary (password stub, converted anonymous method, or unverified email), or the method id is malformed
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ description: Invalid input (bad email/password format)
'401':
- description: Not authenticated
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ description: |
+ Unauthorized - Invalid, tampered, revoked, or wrong-project anon key
'403':
- description: The promoted identity's email domain is not in the project's allowed_email_domains
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Method not found
+ description: |
+ Forbidden - Signups disabled, anon key lacks signup permission, or the
+ email domain is not in `allowed_email_domains`. The internal
+ `anonymous.volcano.internal` domain is reserved for anonymous
+ accounts and is refused whatever the project allows.
+ '429':
+ description: Rate limit exceeded
+ '503':
+ description: Compromised-password screening is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/auth/insights:
- get:
+ /auth/signin:
+ post:
tags:
- - Auth Admin
- summary: Get auth user insights
+ - Authentication
+ summary: Sign in an auth user
description: |
- Returns current auth-user totals, rolling 30-day active users, and
- zero-filled signup and successful sign-in counts for an inclusive UTC
- date range. Weeks start on Monday. Sign-in counts and active-user
- activity begin when collection is deployed. Historical signup counts
- are backfilled from users present at deployment. Token refreshes affect
- active users but not the sign-in series.
- operationId: getAuthInsights
+ Authenticate with email and password. Requires an anon key.
+
+ Set `session_mode` to `cookie` to request HttpOnly refresh-token
+ storage. Cookie mode is honored only for an exact, credentialed CORS
+ origin on the same schemeful site as this API. Otherwise the response
+ retains the refresh token in its body. A frontend on its default
+ Volcano URL is cross-site with this API and so always gets the body
+ token.
+ operationId: authSignin
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: from
- in: query
- description: Inclusive UTC start date. Defaults to 29 days before `to`.
- schema:
- type: string
- format: date
- - name: to
- in: query
- description: Inclusive UTC end date. Defaults to today.
- schema:
- type: string
- format: date
- - name: interval
- in: query
- description: Chart bucket size. Defaults to `day`.
- schema:
- $ref: '#/components/schemas/AuthInsightsInterval'
+ - AnonKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - email
+ - password
+ properties:
+ email:
+ type: string
+ password:
+ type: string
+ session_mode:
+ type: string
+ enum:
+ - cookie
responses:
'200':
- description: Auth insights retrieved
+ description: Signin successful
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthInsightsResponse'
- example:
- project_id: 4f165080-a931-4e03-b3bd-41c45c3f0058
- observed_at: '2026-07-20T18:00:00Z'
- window:
- from: '2026-06-21'
- to: '2026-07-20'
- interval: day
- summary:
- total_users: 1234
- active_users_30d: 418
- series:
- - bucket_start: '2026-07-20'
- signups: 12
- signins: 97
- is_partial: true
+ $ref: '#/components/schemas/AuthTokenResponse'
'400':
- description: Invalid date range or interval
+ description: Invalid input (missing email/password)
+ '401':
+ description: |
+ Unauthorized - Invalid credentials, invalid/tampered/revoked anon key,
+ or account banned/deleted
+ '403':
+ description: |
+ Forbidden - Anon key lacks signin permission, or the email domain is
+ not in `allowed_email_domains` while `allowed_email_domains_mode` is
+ `signup_and_signin`. The domain is taken from the account's canonical
+ email (its primary identity), which is not necessarily the address in
+ the request.
+ '429':
+ description: Rate limit exceeded
+ /auth/refresh:
+ post:
+ tags:
+ - Authentication
+ summary: Refresh access token
+ description: |
+ Get a new access token using a refresh token. Requires an anon key.
+
+ Send `refresh_token` in the body for the default flow. An eligible
+ cookie-mode browser request may instead send `session_mode: cookie`
+ with an empty token or omit the request body; the API reads and resets
+ the project's HttpOnly cookie and omits `refresh_token` from the
+ response.
+ operationId: authRefresh
+ security:
+ - AnonKey: []
+ requestBody:
+ required: false
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ refresh_token:
+ type: string
+ session_mode:
+ type: string
+ enum:
+ - cookie
+ responses:
+ '200':
+ description: Token refreshed
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/AuthTokenResponse'
'401':
- description: Unauthorized - invalid or missing token
+ description: Invalid or expired refresh token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Access denied
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ description: |
+ The account's email domain is not in `allowed_email_domains` while
+ `allowed_email_domains_mode` is `signup_and_signin`, so the session
+ cannot be extended
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '429':
+ description: Rate limit exceeded
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/auth/users:
- get:
+ /auth/logout:
+ post:
tags:
- - Auth Admin
- summary: List all auth users (admin)
- description: List auth users in project. Requires platform token.
- operationId: listAuthUsers
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
- - name: status
- in: query
- required: false
- description: Filter by effective status. `banned` returns only currently-banned users; an expired temporary ban lists as `active`.
- schema:
- type: string
- enum:
- - active
- - banned
+ - Authentication
+ summary: Logout (revoke refresh token)
+ description: |
+ Invalidate a refresh token. Requires an anon key.
+
+ Send `refresh_token` for the default flow. An eligible cookie-mode
+ browser request may instead send `session_mode: cookie` with an empty
+ token; logout remains idempotent when the cookie is missing or expired.
+ operationId: authLogout
+ security:
+ - AnonKey: []
+ requestBody:
+ required: false
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ refresh_token:
+ type: string
+ session_mode:
+ type: string
+ enum:
+ - cookie
+ responses:
+ '204':
+ description: Logged out successfully
+ /auth/forgot-password:
+ post:
+ tags:
+ - Authentication
+ summary: Request password reset
+ description: |
+ Generates recovery token and stores it (email sending pending).
+ Returns generic message to prevent email enumeration.
+ Project is identified via the anon key.
+ operationId: authForgotPassword
+ security:
+ - AnonKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - email
+ properties:
+ email:
+ type: string
+ format: email
responses:
'200':
- description: Successful response
+ description: Generic success message (doesn't reveal if email exists)
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedAuthUsers'
+ type: object
+ properties:
+ message:
+ type: string
+ example: If the email exists, a password reset link has been sent
+ '403':
+ description: Password reset is disabled for this project
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Rate limit exceeded (10 requests per hour per IP)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /auth/reset-password:
+ post:
+ tags:
+ - Authentication
+ summary: Reset password with recovery token
+ description: |
+ Reset password using recovery token from forgot-password.
+ Revokes all existing sessions for security.
+ operationId: authResetPassword
+ security:
+ - AnonKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - token
+ - new_password
+ properties:
+ token:
+ type: string
+ description: Recovery token from forgot-password
+ new_password:
+ type: string
+ description: |
+ Password validated after NFC normalization against the
+ policy returned by GET /auth/password-policy.
+ responses:
+ '200':
+ description: Password reset successful
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
'400':
- description: Invalid status or pagination parameters
+ description: Password doesn't meet requirements
+ '401':
+ description: Invalid or expired token
+ '503':
+ description: Compromised-password screening is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/auth/users/{userId}:
- get:
+ /auth/confirm:
+ post:
tags:
- - Auth Admin
- summary: Get specific auth user (admin)
- operationId: getAuthUser
+ - Authentication
+ summary: Confirm email address
+ description: |
+ Confirm email address using token sent via email.
+ Required if require_email_confirmation is enabled.
+ operationId: authConfirmEmail
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
+ - AnonKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - token
+ properties:
+ token:
+ type: string
+ description: Confirmation token from email
responses:
'200':
- description: Auth user details
+ description: Email confirmed or already confirmed
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthUser'
- delete:
+ type: object
+ properties:
+ message:
+ type: string
+ enum:
+ - Email confirmed successfully
+ - Email already confirmed
+ examples:
+ confirmed:
+ summary: Fresh confirmation
+ value:
+ message: Email confirmed successfully
+ alreadyConfirmed:
+ summary: Token belongs to already-confirmed user
+ value:
+ message: Email already confirmed
+ '400':
+ description: Missing confirmation token in request body
+ '401':
+ description: Invalid or expired token
+ /auth/resend-confirmation:
+ post:
tags:
- - Auth Admin
- summary: Delete auth user (admin)
- description: Soft-deletes user and revokes all sessions
- operationId: deleteAuthUser
+ - Authentication
+ summary: Resend confirmation email
+ description: |
+ Resend email confirmation link.
+ Returns generic message to prevent email enumeration.
+ No email is sent when the account does not exist or is already confirmed.
+ If the account exists and is unconfirmed, a new token is generated and
+ any previous confirmation token is invalidated.
+ operationId: authResendConfirmation
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
+ - AnonKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - email
+ properties:
+ email:
+ type: string
+ format: email
responses:
- '204':
- description: User deleted
- /projects/{id}/auth/users/{userId}/sessions:
- get:
+ '200':
+ description: Generic success message
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ '429':
+ description: Rate limit exceeded
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /auth/signup-anonymous:
+ post:
tags:
- - Auth Admin
- summary: List user sessions
+ - Authentication
+ summary: Create anonymous user
description: |
- List paginated sessions for a specific auth user.
- Returns session details including device info, IP address, and activity timestamps.
+ Create guest user without email/password.
- Ordering and pagination match `GET /auth/user/sessions`: the default is
- activity order with `page`/`limit` and the legacy `sessions` body, and
- `sort=created_at` opts into the standard cursor/offset hybrid with the
- shared `data` envelope. Cursor pagination is only available for
- `sort=created_at`, because the activity timestamp changes under paging.
- The `status=expired` filter is offset-only because sessions can expire
- above a cursor anchor during a walk.
- operationId: listUserSessions
+ User metadata (like display_name) can be included and will appear in realtime presence events.
+ Requires enable_anonymous_signins to be true.
+ operationId: authSignupAnonymous
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- - name: page
- in: query
- description: Page number (1-indexed)
- schema:
- type: integer
- minimum: 1
- default: 1
- - name: limit
- in: query
- description: Number of sessions per page (max 100)
- schema:
- type: integer
- minimum: 1
- maximum: 100
- default: 20
- - name: sort
- in: query
- description: |
- Sort key. `last_activity` (default) orders by most recent activity and
- supports offset pagination only. `created_at` orders by session start
- and supports both offset and cursor pagination.
- schema:
- type: string
- enum:
- - last_activity
- - created_at
- default: last_activity
- - name: status
- in: query
- description: |
- Filter by whether the session can still be refreshed. Omit for every
- stored session, including expired ones. `expired` is not supported
- with cursor pagination.
- schema:
- type: string
- enum:
- - active
- - expired
- - name: cursor
- in: query
- description: |
- Opaque keyset cursor from a previous response's `next_cursor`. Requires
- `sort=created_at`; mutually exclusive with `page` and `ending_before`.
- schema:
- type: string
- - name: ending_before
- in: query
- description: |
- Opaque keyset cursor from a previous response's `prev_cursor`, paging
- backward. Requires `sort=created_at`; mutually exclusive with `page`
- and `cursor`.
- schema:
- type: string
- - name: offset
- in: query
- description: |
- Bounded number of rows to skip past the cursor anchor (the hybrid
- jump, maximum 100000). Ignored unless `cursor` or `ending_before` is
- supplied.
- schema:
- type: integer
- minimum: 0
- maximum: 100000
+ - AnonKey: []
+ requestBody:
+ description: Optional user metadata
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ user_metadata:
+ type: object
+ additionalProperties: true
+ description: Custom user metadata (e.g., display_name, avatar_url)
+ example:
+ display_name: Alice
+ avatar_url: https://example.com/alice.jpg
responses:
- '200':
- description: Paginated list of user sessions
- content:
- application/json:
- schema:
- type: object
- properties:
- sessions:
- type: array
- items:
- $ref: '#/components/schemas/AuthSession'
- total:
- type: integer
- description: Total number of sessions
- page:
- type: integer
- description: Current page number
- limit:
- type: integer
- description: Number of sessions per page
- total_pages:
- type: integer
- description: Total number of pages
- data:
- type: array
- description: Sessions for this page (cursor pagination only)
- items:
- $ref: '#/components/schemas/AuthSession'
- has_more:
- type: boolean
- description: Whether a further page exists (cursor pagination only)
- next_cursor:
- type: string
- description: Opaque cursor for the next page (cursor pagination only)
- prev_cursor:
- type: string
- description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
- '400':
- description: Invalid or conflicting pagination parameters
+ '201':
+ description: Anonymous user created
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: User not found
- delete:
- tags:
- - Auth Admin
- summary: Delete all user sessions
- description: |
- Revokes all sessions for a user, forcing them to re-authenticate on all devices.
- Use this to log out a user from everywhere.
- operationId: deleteAllUserSessions
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- responses:
- '204':
- description: All sessions deleted
- '404':
- description: User not found
- /projects/{id}/auth/users/{userId}/sessions/{sessionId}:
- delete:
- tags:
- - Auth Admin
- summary: Delete specific session
- description: |
- Revokes a specific session for a user.
- Use this to log out a user from a single device.
- operationId: deleteUserSession
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- - name: sessionId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- responses:
- '204':
- description: Session deleted
- '404':
- description: Session or user not found
- /projects/{id}/auth/users/{userId}/ban:
+ $ref: '#/components/schemas/AuthTokenResponse'
+ '403':
+ description: Anonymous signins disabled
+ /auth/user/convert-anonymous:
post:
tags:
- - Auth Admin
- summary: Ban a user
+ - Authentication
+ summary: Convert anonymous user to authenticated
description: |
- Bans a user temporarily or permanently. Banned users cannot sign in
- and all their active sessions are immediately revoked.
-
- - Omit `banned_until` for a permanent ban
- - Provide `banned_until` ISO timestamp for a temporary ban
- operationId: banAuthUser
+ Add email and password to anonymous user.
+ Requires auth user access token.
+ If require_email_confirmation is enabled for the project, the converted
+ user remains unconfirmed until /auth/confirm succeeds. When email
+ sending is enabled, a confirmation email is sent during conversion.
+ operationId: authConvertAnonymous
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
+ - AuthUserAccessToken: []
requestBody:
+ required: true
content:
application/json:
schema:
type: object
+ required:
+ - email
+ - password
properties:
- banned_until:
+ email:
type: string
- format: date-time
- description: When the ban expires (omit for permanent ban)
- example: '2026-12-31T23:59:59Z'
+ format: email
+ password:
+ type: string
+ description: |
+ Password validated after NFC normalization against the
+ policy returned by GET /auth/password-policy.
+ user_metadata:
+ type: object
+ additionalProperties: true
responses:
'200':
- description: User banned successfully
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/BanUserResponse'
- '404':
- description: User not found
+ description: User converted successfully
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/auth/users/{userId}/unban:
- post:
- tags:
- - Auth Admin
- summary: Unban a user
- description: |
- Removes a ban from a user, restoring their ability to sign in.
- The user's status is set back to 'active'.
- operationId: unbanAuthUser
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: userId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- responses:
- '200':
- description: User unbanned successfully
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UnbanUserResponse'
- '404':
- description: User not found
+ type: object
+ properties:
+ user:
+ $ref: '#/components/schemas/AuthUser'
+ '400':
+ description: Not an anonymous user
+ '403':
+ description: |
+ The chosen email domain is not in `allowed_email_domains`
+ '409':
+ description: Email already in use
+ '503':
+ description: Compromised-password screening is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/email-templates:
- get:
- tags:
- - Auth Configuration
- summary: List email templates
- description: Returns all custom email templates for this project.
- operationId: listEmailTemplates
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- responses:
- '200':
- description: Email templates list
- content:
- application/json:
- schema:
- type: object
- properties:
- data:
- type: array
- items:
- $ref: '#/components/schemas/EmailTemplate'
+ /auth/user/change-email:
post:
tags:
- - Auth Configuration
- summary: Create email template
+ - Authentication
+ summary: Request email change
description: |
- Creates a custom email template for the project. Custom email templates
- are a PRO-plan feature: requests from a FREE-plan project owner are
- rejected with 403, and FREE projects always send the built-in default
- templates regardless of any previously saved custom rows.
- Every project is created with one template per type, so customizing one
- is usually a PUT; creating a type the project already has returns 409.
- Valid template types: welcome, confirmation, password_reset, password_changed
- operationId: createEmailTemplate
+ Request to change user's email address.
+ Sends confirmation token to new email address.
+ operationId: authRequestEmailChange
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - AuthUserAccessToken: []
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateEmailTemplateRequest'
+ type: object
+ required:
+ - new_email
+ properties:
+ new_email:
+ type: string
+ format: email
responses:
- '201':
- description: Template created
+ '200':
+ description: Confirmation email sent
content:
application/json:
schema:
- $ref: '#/components/schemas/EmailTemplate'
+ type: object
+ properties:
+ message:
+ type: string
+ new_email:
+ type: string
'400':
- description: Invalid template type
+ description: Invalid email format or same as current email
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Custom email templates require the PRO plan
+ description: |
+ The requested email domain is not in `allowed_email_domains`
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: The project already has a template of this type
+ description: Email already in use
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/email-templates/{type}:
- get:
- tags:
- - Auth Configuration
- summary: Get email template
- operationId: getEmailTemplate
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: type
- in: path
- required: true
- schema:
- type: string
- enum:
- - welcome
- - confirmation
- - password_reset
- - password_changed
- responses:
- '200':
- description: Email template
+ '429':
+ description: Rate limit exceeded (10 requests per hour per IP)
content:
application/json:
schema:
- $ref: '#/components/schemas/EmailTemplate'
- '404':
- description: Template not found
- put:
+ $ref: '#/components/schemas/Error'
+ /auth/user/confirm-email-change:
+ post:
tags:
- - Auth Configuration
- summary: Update email template
- description: |
- Updates a custom email template. Custom email templates are a PRO-plan
- feature: requests from a FREE-plan project owner are rejected with 403
- (including after a PRO→FREE downgrade), so a FREE project cannot modify
- templates and always sends the built-in defaults.
- operationId: updateEmailTemplate
+ - Authentication
+ summary: Confirm email change
+ description: Confirm email change with token sent to new address
+ operationId: authConfirmEmailChange
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: type
- in: path
- required: true
- schema:
- type: string
- enum:
- - welcome
- - confirmation
- - password_reset
- - password_changed
+ - AuthUserAccessToken: []
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateEmailTemplateRequest'
+ type: object
+ required:
+ - email_change_token
+ properties:
+ email_change_token:
+ type: string
responses:
'200':
- description: Template updated
+ description: Email changed successfully
content:
application/json:
schema:
- $ref: '#/components/schemas/EmailTemplate'
- '403':
- description: Custom email templates require the PRO plan
+ type: object
+ properties:
+ message:
+ type: string
+ user:
+ $ref: '#/components/schemas/AuthUser'
+ '400':
+ description: Invalid or expired token, or no pending email change
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Template not found
- delete:
- tags:
- - Auth Configuration
- summary: Delete email template
- description: |
- Deletes a custom template, reverting to the default. Custom email
- templates are a PRO-plan feature: requests from a FREE-plan project owner
- are rejected with 403.
- operationId: deleteEmailTemplate
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: type
- in: path
- required: true
- schema:
- type: string
- enum:
- - welcome
- - confirmation
- - password_reset
- - password_changed
- responses:
- '204':
- description: Template deleted
'403':
- description: Custom email templates require the PRO plan
+ description: |
+ The pending email domain is no longer in `allowed_email_domains`.
+ Re-checked here because the allowlist can narrow between the request
+ and the confirmation.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Template not found
- /email-templates/defaults:
- get:
+ '409':
+ description: Email is now in use by another user
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /auth/user/cancel-email-change:
+ delete:
tags:
- - Auth Configuration
- summary: Get default email templates
- description: Returns the default email templates used when no custom template is configured.
- operationId: getDefaultEmailTemplates
+ - Authentication
+ summary: Cancel pending email change
+ operationId: authCancelEmailChange
+ security:
+ - AuthUserAccessToken: []
responses:
'200':
- description: Default templates
+ description: Email change cancelled
content:
application/json:
schema:
type: object
properties:
- data:
- type: array
- items:
- $ref: '#/components/schemas/EmailTemplate'
- /email-templates/defaults/{type}:
- get:
- tags:
- - Auth Configuration
- summary: Get default email template by type
- operationId: getDefaultEmailTemplate
+ message:
+ type: string
+ '401':
+ description: Not authenticated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /auth/user/sessions:
+ get:
+ tags:
+ - Authentication
+ summary: Get current user's sessions
+ description: |
+ Returns paginated sessions for the currently authenticated user.
+ Each session includes device info, IP addresses, and activity timestamps.
+ The current session is marked with `is_current: true`.
+
+ **Ordering and pagination.** Without `sort`, results are ordered by most
+ recent activity and paged with `page`/`limit`, returning the
+ `sessions`/`total`/`page`/`limit`/`total_pages` body below. This is the
+ legacy default and is preserved for existing clients.
+
+ Send `sort=created_at` to opt into the standard list contract: results are
+ ordered by session start (newest first) and may be paged either with
+ `page`/`limit` or by cursor with `cursor`/`ending_before` plus a bounded
+ `offset` past the cursor anchor. Cursor responses use the shared
+ `data` envelope with `next_cursor`/`prev_cursor`.
+
+ Unlike other list endpoints, sending `limit` without `page` does **not**
+ select cursor mode here; `sort=created_at` is the only opt-in. Cursor
+ pagination is rejected with 400 for the activity order, because
+ `last_activity_at` changes whenever a session refreshes its token: a row
+ that crosses the cursor anchor between two requests would be skipped and
+ never shown. The `status=expired` filter is also offset-only because a
+ session can expire above the cursor anchor during a walk. Sending that
+ filter in cursor mode, `page` with `cursor` or `ending_before`, or both
+ cursor directions returns 400.
+ operationId: authGetMySessions
+ security:
+ - AuthUserAccessToken: []
parameters:
- - name: type
- in: path
- required: true
+ - name: page
+ in: query
+ description: Page number (1-indexed)
+ schema:
+ type: integer
+ minimum: 1
+ default: 1
+ - name: limit
+ in: query
+ description: Number of sessions per page (max 100)
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 100
+ default: 20
+ - name: sort
+ in: query
+ description: |
+ Sort key. `last_activity` (default) orders by most recent activity and
+ supports offset pagination only. `created_at` orders by session start
+ and supports both offset and cursor pagination.
schema:
type: string
enum:
- - welcome
- - confirmation
- - password_reset
- - password_changed
+ - last_activity
+ - created_at
+ default: last_activity
+ - name: status
+ in: query
+ description: |
+ Filter by whether the session can still be refreshed. Omit for every
+ stored session, including expired ones. `expired` is not supported
+ with cursor pagination.
+ schema:
+ type: string
+ enum:
+ - active
+ - expired
+ - name: cursor
+ in: query
+ description: |
+ Opaque keyset cursor from a previous response's `next_cursor`. Requires
+ `sort=created_at`; mutually exclusive with `page` and `ending_before`.
+ schema:
+ type: string
+ - name: ending_before
+ in: query
+ description: |
+ Opaque keyset cursor from a previous response's `prev_cursor`, paging
+ backward. Requires `sort=created_at`; mutually exclusive with `page`
+ and `cursor`.
+ schema:
+ type: string
+ - name: offset
+ in: query
+ description: |
+ Bounded number of rows to skip past the cursor anchor (the hybrid
+ jump, maximum 100000). Ignored unless `cursor` or `ending_before` is
+ supplied.
+ schema:
+ type: integer
+ minimum: 0
+ maximum: 100000
responses:
'200':
- description: Default template
+ description: Paginated list of user sessions
content:
application/json:
schema:
- $ref: '#/components/schemas/EmailTemplate'
- '404':
- description: Template type not found
- /projects/{id}/auth/config:
- get:
+ type: object
+ properties:
+ sessions:
+ type: array
+ items:
+ $ref: '#/components/schemas/AuthSession'
+ total:
+ type: integer
+ description: Total number of sessions
+ page:
+ type: integer
+ description: Current page number
+ limit:
+ type: integer
+ description: Number of sessions per page
+ total_pages:
+ type: integer
+ description: Total number of pages
+ data:
+ type: array
+ description: Sessions for this page (cursor pagination only)
+ items:
+ $ref: '#/components/schemas/AuthSession'
+ has_more:
+ type: boolean
+ description: Whether a further page exists (cursor pagination only)
+ next_cursor:
+ type: string
+ description: Opaque cursor for the next page (cursor pagination only)
+ prev_cursor:
+ type: string
+ description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
+ '400':
+ description: Invalid or conflicting pagination parameters
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Not authenticated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ delete:
tags:
- - Auth Configuration
- summary: Get auth configuration
- operationId: getAuthConfig
+ - Authentication
+ summary: Sign out from all other devices
+ description: |
+ Deletes all sessions except the current one.
+ Use this to log out from all other devices while keeping the current session active.
+ operationId: authDeleteAllMySessions
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - AuthUserAccessToken: []
responses:
- '200':
- description: Auth configuration
+ '204':
+ description: All other sessions deleted
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthConfig'
- put:
+ $ref: '#/components/schemas/Error'
+ /auth/user/sessions/{sessionId}:
+ delete:
tags:
- - Auth Configuration
- summary: Update auth configuration
+ - Authentication
+ summary: Sign out from specific device
description: |
- Updates the project's auth configuration. Only the fields present in
- the body are changed.
- operationId: updateAuthConfig
+ Deletes a specific session, logging out that device.
+ You can get session IDs from the list sessions endpoint.
+ operationId: authDeleteMySession
security:
- - UserToken: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UpdateAuthConfigRequest'
+ - name: sessionId
+ in: path
+ required: true
+ description: The session ID to delete
+ schema:
+ type: string
+ format: uuid
responses:
- '200':
- description: Configuration updated
+ '204':
+ description: Session deleted
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthConfig'
- '403':
- description: |
- The update would turn on, widen, or otherwise edit the email domain
- allowlist (`allowed_email_domains`, `allowed_email_domains_mode`)
- for a project that is not on the PRO plan. A FREE project keeps
- whatever allowlist it already has — parked, enforcing nothing until
- it upgrades — and may still remove it, so a downgrade never leaves a
- project locked out of its own signups.
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Session not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/auth/config/test-email:
- post:
+ /auth/user:
+ get:
tags:
- - Auth Configuration
- summary: Send a test email using the project's saved SMTP config
- description: |
- Sends a diagnostic email to `to_email` using the project's
- persisted `auth_config` SMTP credentials. If `html_body` or
- `text_body` is supplied, the override path is taken: those
- values (plus optional `subject`) are rendered through
- html/text templates against the project's `Data` and
- used as the body — used by the template editor's "Send Test"
- affordance to preview an unsaved template. With both bodies
- omitted, a hardcoded diagnostic message is sent and any
- `subject` field is ignored. Sending `subject` alone (no
- bodies) is rejected with 400 to avoid a silently-dropped
- subject or a blank message. Also rejects with 400 if
- `email_enabled=false` or `smtp_host` is empty.
- operationId: testEmailConfig
+ - Authentication
+ summary: Get current user profile
+ description: Returns authenticated user's profile. Requires access token.
+ operationId: authGetUser
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/TestEmailRequest'
+ - AuthUserAccessToken: []
responses:
'200':
- description: Test email sent
+ description: User profile
content:
application/json:
schema:
- $ref: '#/components/schemas/TestEmailResponse'
- '400':
- description: Invalid request or email delivery not configured
+ type: object
+ properties:
+ user:
+ $ref: '#/components/schemas/AuthUser'
'401':
- description: Unauthorized
- '403':
- description: Forbidden
- '404':
- description: Project not found
- '502':
- description: Template render failure or SMTP delivery failed
- /projects/{id}/auth/hosted-pages/{pageType}:
- get:
- tags:
- - Auth Configuration
- summary: Get hosted auth page
- description: |
- Returns the saved HTML/CSS for the page type, or `page: null` when the
- project has not customized it yet. Always returns `defaults` (the theme
- shell to seed an editor with, which is valid input to the update endpoint)
- and `runtime` (the script the rendered page runs, plus a preview harness).
- operationId: getAuthHostedPage
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: pageType
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/HostedAuthPageType'
- responses:
- '200':
- description: Hosted page loaded
+ description: Not authenticated - access token missing or invalid
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthHostedPageResponse'
+ $ref: '#/components/schemas/Error'
put:
tags:
- - Auth Configuration
- summary: Update hosted auth page
- description: |
- Saves the current HTML/CSS for this page type.
- Security validation rejects script tags, javascript: URLs, inline event handlers, iframe/object/embed/meta/link tags in HTML,
- and closing style/head tags in CSS.
- operationId: updateAuthHostedPage
+ - Authentication
+ summary: Update user profile
+ description: Update password or metadata. Requires access token.
+ operationId: authUpdateUser
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: pageType
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/HostedAuthPageType'
+ - AuthUserAccessToken: []
requestBody:
- required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateAuthHostedPageRequest'
+ type: object
+ properties:
+ password:
+ type: string
+ description: |
+ Password validated after NFC normalization against the
+ policy returned by GET /auth/password-policy.
+ user_metadata:
+ type: object
+ additionalProperties: true
+ description: |
+ Metadata keys to merge into the current user metadata.
+ Omitted keys remain unchanged; set a key to null to remove it.
+ Merging is shallow; nested objects replace the stored value for that top-level key.
responses:
'200':
- description: Hosted page updated
+ description: Profile updated
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthHostedPageResponse'
+ type: object
+ properties:
+ user:
+ $ref: '#/components/schemas/AuthUser'
'400':
- description: Invalid input or unsafe markup
- '401':
- description: Unauthorized
- '403':
- description: Forbidden
- '404':
- description: Project not found
- /projects/{id}/auth/pages/appearance:
- get:
- tags:
- - Auth Configuration
- summary: Get managed auth page appearance
- operationId: getAuthPageAppearance
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- responses:
- '200':
- description: Saved appearance and effective plan state
+ description: Bad request - invalid password or metadata format
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthPageAppearanceResponse'
+ $ref: '#/components/schemas/Error'
'401':
- description: Unauthorized
+ description: Not authenticated - access token missing or invalid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Access denied
+ '503':
+ description: Compromised-password screening is temporarily unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ /auth/user/identities:
+ get:
+ tags:
+ - Authentication
+ summary: List the current user's identities
+ description: |
+ Returns every real email identity the account owns. An account can own
+ multiple identities (for example a password identity plus one or more
+ OAuth identities on different emails). Anonymous accounts have no real
+ identity and return an empty list.
+ operationId: authListIdentities
+ security:
+ - AuthUserAccessToken: []
+ responses:
+ '200':
+ description: List of identities
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Appearance could not be read
+ $ref: '#/components/schemas/AuthIdentitiesResponse'
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/auth/pages/theme:
- put:
+ /auth/user/identities/{identityId}:
+ delete:
tags:
- - Auth Configuration
- summary: Save the managed auth page theme
- operationId: updateAuthPageTheme
+ - Authentication
+ summary: Unlink an identity from the current user
+ description: |
+ Removes a non-primary identity and its attached sign-in methods. Refused
+ when the identity is the account's primary, its only identity, or when
+ removing it would leave the account with no way to sign in.
+ operationId: authUnlinkIdentity
security:
- - UserToken: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UpdateAuthPageThemeRequest'
+ - name: identityId
+ in: path
+ required: true
+ description: The identity ID to unlink
+ schema:
+ type: string
+ format: uuid
responses:
- '200':
- description: Theme saved
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UpdateAuthPageThemeRequest'
+ '204':
+ description: Identity unlinked
'400':
- description: Invalid or unreadable theme
+ description: Identity cannot be unlinked (primary, last, or would remove last sign-in method), or the identity id is malformed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Unauthorized
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: Plan does not permit customisation
+ '404':
+ description: Identity not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ /auth/user/methods:
+ get:
+ tags:
+ - Authentication
+ summary: List the current user's sign-in methods
+ description: |
+ Returns a flat list of every sign-in method the account owns (password,
+ each OAuth provider, and any active anonymous method), with the primary
+ method flagged. Password stubs and converted anonymous methods are excluded.
+ operationId: authListMethods
+ security:
+ - AuthUserAccessToken: []
+ responses:
+ '200':
+ description: List of sign-in methods
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Theme could not be saved
+ $ref: '#/components/schemas/AuthMethodsResponse'
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ /auth/user/methods/{methodId}/promote:
+ post:
tags:
- - Auth Configuration
- summary: Clear the managed auth page theme
- operationId: deleteAuthPageTheme
+ - Authentication
+ summary: Set a method as the account's primary
+ description: |
+ Promotes the given method to the account's primary sign-in method. The
+ account's canonical email is re-derived from the promoted method's identity.
+ Refused for password stubs and converted anonymous methods, and for an
+ identity whose domain is outside the project's `allowed_email_domains`.
+ operationId: authPromoteMethod
security:
- - UserToken: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - name: methodId
+ in: path
+ required: true
+ description: The method ID to promote
+ schema:
+ type: string
+ format: uuid
responses:
- '204':
- description: Theme cleared
- '401':
- description: Unauthorized
+ '200':
+ description: The promoted method
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Plan does not permit customisation
+ $ref: '#/components/schemas/AuthMethodSummary'
+ '400':
+ description: This method cannot be set as primary (password stub, converted anonymous method, or unverified email), or the method id is malformed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: Project not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Theme could not be cleared
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/auth/pages/{pageType}/layout:
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: pageType
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/HostedAuthPageType'
- put:
- tags:
- - Auth Configuration
- summary: Save one managed auth page layout
- operationId: updateAuthPageLayout
- security:
- - UserToken: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UpdateAuthPageLayoutRequest'
- responses:
- '200':
- description: Layout saved
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UpdateAuthPageLayoutRequest'
- '400':
- description: Invalid page type or layout
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Plan does not permit customisation
+ description: The promoted identity's email domain is not in the project's allowed_email_domains
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Project not found
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Layout could not be saved
+ description: Method not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ /projects/{id}/auth/insights:
+ get:
tags:
- - Auth Configuration
- summary: Clear one managed auth page layout
- operationId: deleteAuthPageLayout
+ - Auth Admin
+ summary: Get auth user insights
+ description: |
+ Returns current auth-user totals, rolling 30-day active users, and
+ zero-filled signup and successful sign-in counts for an inclusive UTC
+ date range. Weeks start on Monday. Sign-in counts and active-user
+ activity begin when collection is deployed. Historical signup counts
+ are backfilled from users present at deployment. Token refreshes affect
+ active users but not the sign-in series.
+ operationId: getAuthInsights
security:
- UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: from
+ in: query
+ description: Inclusive UTC start date. Defaults to 29 days before `to`.
+ schema:
+ type: string
+ format: date
+ - name: to
+ in: query
+ description: Inclusive UTC end date. Defaults to today.
+ schema:
+ type: string
+ format: date
+ - name: interval
+ in: query
+ description: Chart bucket size. Defaults to `day`.
+ schema:
+ $ref: '#/components/schemas/AuthInsightsInterval'
responses:
- '204':
- description: Layout cleared
+ '200':
+ description: Auth insights retrieved
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthInsightsResponse'
+ example:
+ project_id: 4f165080-a931-4e03-b3bd-41c45c3f0058
+ observed_at: '2026-07-20T18:00:00Z'
+ window:
+ from: '2026-06-21'
+ to: '2026-07-20'
+ interval: day
+ summary:
+ total_users: 1234
+ active_users_30d: 418
+ series:
+ - bucket_start: '2026-07-20'
+ signups: 12
+ signins: 97
+ is_partial: true
'400':
- description: Invalid page type
+ description: Invalid date range or interval
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Unauthorized
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
- description: Plan does not permit customisation
+ description: Access denied
content:
application/json:
schema:
@@ -9256,405 +9474,407 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'500':
- description: Layout could not be cleared
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/auth/pages/{pageType}/preview:
+ /projects/{id}/auth/users:
get:
tags:
- - Auth Configuration
- summary: Render a short-lived managed auth page preview
- description: |
- Public HTML endpoint for a preview URL returned by the POST operation.
- The signed ticket contains the unsaved appearance, expires shortly, and
- runs the production page runtime against mocked authentication responses.
- operationId: renderAuthPagePreview
- security: []
+ - Auth Admin
+ summary: List all auth users (admin)
+ description: List auth users in project. Requires platform token.
+ operationId: listAuthUsers
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: pageType
- in: path
- required: true
- schema:
- $ref: '#/components/schemas/HostedAuthPageType'
- - name: ticket
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
+ - name: status
in: query
- required: true
+ required: false
+ description: Filter by effective status. `banned` returns only currently-banned users; an expired temporary ban lists as `active`.
schema:
type: string
- minLength: 1
- maxLength: 4096
- description: Short-lived signed preview ticket returned by the POST operation.
+ enum:
+ - active
+ - banned
responses:
'200':
- description: Rendered preview document
- content:
- text/html:
- schema:
- type: string
- '404':
- description: Ticket is invalid or expired, its path does not match, or managed authentication is disabled
+ description: Successful response
content:
- text/plain:
+ application/json:
schema:
- type: string
- '500':
- description: Preview could not be rendered
+ $ref: '#/components/schemas/PaginatedAuthUsers'
+ '400':
+ description: Invalid status or pagination parameters
content:
- text/plain:
+ application/json:
schema:
- type: string
- post:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/auth/users/{userId}:
+ get:
tags:
- - Auth Configuration
- summary: Preview an unsaved managed auth page appearance
- operationId: previewAuthPage
+ - Auth Admin
+ summary: Get specific auth user (admin)
+ operationId: getAuthUser
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: pageType
+ - name: userId
in: path
required: true
schema:
- $ref: '#/components/schemas/HostedAuthPageType'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PreviewAuthPageRequest'
+ type: string
+ format: uuid
responses:
'200':
- description: Short-lived URL for the rendered preview document
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PreviewAuthPageResponse'
- '400':
- description: Invalid page type or draft
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: Access denied
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project not found or managed authentication is disabled
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Preview could not be rendered
+ description: Auth user details
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/auth/hosted/{pageType}:
- get:
+ $ref: '#/components/schemas/AuthUser'
+ delete:
tags:
- - Auth Configuration
- summary: Render a managed auth page
- description: |
- Public HTML endpoint for signup, forgot-password, device approval,
- verify-email, and reset-password pages. Login uses the path without a
- page type.
- Requires `Accept: text/html`.
- Returns 404 when managed hosted pages are disabled for the project.
- security: []
- operationId: renderManagedAuthPage
+ - Auth Admin
+ summary: Delete auth user (admin)
+ description: Soft-deletes user and revokes all sessions
+ operationId: deleteAuthUser
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: pageType
+ - name: userId
in: path
required: true
schema:
- $ref: '#/components/schemas/HostedRenderablePageType'
+ type: string
+ format: uuid
responses:
- '200':
- description: Hosted auth page HTML
- content:
- text/html:
- schema:
- type: string
- '400':
- description: Invalid project id or unsupported Accept header
- '404':
- description: Managed pages disabled or page type not found
- /projects/{id}/auth/hosted:
+ '204':
+ description: User deleted
+ /projects/{id}/auth/users/{userId}/sessions:
get:
tags:
- - Auth Configuration
- summary: Render default managed auth page
+ - Auth Admin
+ summary: List user sessions
description: |
- Public HTML endpoint for the managed login page.
- Requires `Accept: text/html`.
- security: []
- operationId: renderDefaultManagedAuthPage
+ List paginated sessions for a specific auth user.
+ Returns session details including device info, IP address, and activity timestamps.
+
+ Ordering and pagination match `GET /auth/user/sessions`: the default is
+ activity order with `page`/`limit` and the legacy `sessions` body, and
+ `sort=created_at` opts into the standard cursor/offset hybrid with the
+ shared `data` envelope. Cursor pagination is only available for
+ `sort=created_at`, because the activity timestamp changes under paging.
+ The `status=expired` filter is offset-only because sessions can expire
+ above a cursor anchor during a walk.
+ operationId: listUserSessions
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: action
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ - name: page
in: query
- required: false
+ description: Page number (1-indexed)
+ schema:
+ type: integer
+ minimum: 1
+ default: 1
+ - name: limit
+ in: query
+ description: Number of sessions per page (max 100)
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 100
+ default: 20
+ - name: sort
+ in: query
+ description: |
+ Sort key. `last_activity` (default) orders by most recent activity and
+ supports offset pagination only. `created_at` orders by session start
+ and supports both offset and cursor pagination.
schema:
type: string
enum:
- - login
- - signup
- - forgot-password
- - device
- description: |
- Optional deep-link action for the unified hosted page. Ignored when a
- custom login page is configured. `action=device` renders the device
- authorization approval UI inline (no redirect to any external app);
- it signs the user in and calls `POST /auth/device/verify`.
- - name: user_code
+ - last_activity
+ - created_at
+ default: last_activity
+ - name: status
in: query
- required: false
+ description: |
+ Filter by whether the session can still be refreshed. Omit for every
+ stored session, including expired ones. `expired` is not supported
+ with cursor pagination.
schema:
type: string
- description: Device user code (from `POST /auth/device/authorize`) used with `action=device`.
- - name: anon_key
+ enum:
+ - active
+ - expired
+ - name: cursor
in: query
- required: false
+ description: |
+ Opaque keyset cursor from a previous response's `next_cursor`. Requires
+ `sort=created_at`; mutually exclusive with `page` and `ending_before`.
schema:
type: string
- description: Project anon key used by built-in managed auth flows (required for login/signup/device actions).
- - name: state
+ - name: ending_before
in: query
- required: false
+ description: |
+ Opaque keyset cursor from a previous response's `prev_cursor`, paging
+ backward. Requires `sort=created_at`; mutually exclusive with `page`
+ and `cursor`.
schema:
type: string
+ - name: offset
+ in: query
description: |
- Opaque one-time nonce generated by the client SDK before redirecting
- here. On successful login/signup it is echoed back in the post-auth
- redirect fragment as `state`, so the SDK can bind the returned session
- to the flow it initiated (login-CSRF / session-fixation defense). The
- SDK rejects a returned session whose `state` does not match.
+ Bounded number of rows to skip past the cursor anchor (the hybrid
+ jump, maximum 100000). Ignored unless `cursor` or `ending_before` is
+ supplied.
+ schema:
+ type: integer
+ minimum: 0
+ maximum: 100000
responses:
'200':
- description: Hosted auth page HTML
+ description: Paginated list of user sessions
content:
- text/html:
+ application/json:
schema:
- type: string
+ type: object
+ properties:
+ sessions:
+ type: array
+ items:
+ $ref: '#/components/schemas/AuthSession'
+ total:
+ type: integer
+ description: Total number of sessions
+ page:
+ type: integer
+ description: Current page number
+ limit:
+ type: integer
+ description: Number of sessions per page
+ total_pages:
+ type: integer
+ description: Total number of pages
+ data:
+ type: array
+ description: Sessions for this page (cursor pagination only)
+ items:
+ $ref: '#/components/schemas/AuthSession'
+ has_more:
+ type: boolean
+ description: Whether a further page exists (cursor pagination only)
+ next_cursor:
+ type: string
+ description: Opaque cursor for the next page (cursor pagination only)
+ prev_cursor:
+ type: string
+ description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
'400':
- description: Invalid project id or unsupported Accept header
+ description: Invalid or conflicting pagination parameters
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Managed pages disabled
- /projects/{id}/auth/hosted/login/options:
- get:
+ description: User not found
+ delete:
tags:
- - Auth Configuration
- summary: Get hosted login runtime options
+ - Auth Admin
+ summary: Delete all user sessions
description: |
- Returns runtime options for the built-in managed login flow.
- Requires `anon_key` query parameter.
- Rate limited per project and client IP. Excess requests return `429` and `Retry-After`.
- security: []
- operationId: getHostedLoginOptions
+ Revokes all sessions for a user, forcing them to re-authenticate on all devices.
+ Use this to log out a user from everywhere.
+ operationId: deleteAllUserSessions
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: anon_key
- in: query
+ - name: userId
+ in: path
required: true
schema:
type: string
+ format: uuid
responses:
- '200':
- description: Hosted login options returned
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/HostedLoginOptionsResponse'
- '401':
- description: Invalid or missing anon key
+ '204':
+ description: All sessions deleted
'404':
- description: Managed pages disabled
- '429':
- description: Rate limit exceeded
- /projects/{id}/auth/hosted/login/check-email:
+ description: User not found
+ /projects/{id}/auth/users/{userId}/sessions/{sessionId}:
+ delete:
+ tags:
+ - Auth Admin
+ summary: Delete specific session
+ description: |
+ Revokes a specific session for a user.
+ Use this to log out a user from a single device.
+ operationId: deleteUserSession
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ - name: sessionId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '204':
+ description: Session deleted
+ '404':
+ description: Session or user not found
+ /projects/{id}/auth/users/{userId}/ban:
post:
tags:
- - Auth Configuration
- summary: Check whether email exists for hosted login flow
+ - Auth Admin
+ summary: Ban a user
description: |
- Used by the built-in managed login page to branch UI between signin and signup.
- Requires anon key in Authorization header.
- Rate limited per project and client IP. Excess requests return `429` and `Retry-After`.
- security: []
- operationId: hostedLoginCheckEmail
+ Bans a user temporarily or permanently. Banned users cannot sign in
+ and all their active sessions are immediately revoked.
+
+ - Omit `banned_until` for a permanent ban
+ - Provide `banned_until` ISO timestamp for a temporary ban
+ operationId: banAuthUser
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: Authorization
- in: header
+ - name: userId
+ in: path
required: true
schema:
type: string
- description: Bearer anon key (`Bearer `)
+ format: uuid
requestBody:
- required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/HostedLoginEmailCheckRequest'
- responses:
- '200':
- description: Email existence evaluated
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/HostedLoginEmailCheckResponse'
- '401':
- description: Invalid or missing anon key
- '429':
- description: Rate limit exceeded
- /projects/{id}/auth/methods:
- get:
- tags:
- - Auth Configuration
- summary: Get all authentication methods
- description: |
- Returns all configured authentication methods for this project,
- including email/password, anonymous, device authorization, and OAuth providers.
- operationId: getAuthMethods
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ type: object
+ properties:
+ banned_until:
+ type: string
+ format: date-time
+ description: When the ban expires (omit for permanent ban)
+ example: '2026-12-31T23:59:59Z'
responses:
'200':
- description: Authentication methods configuration
+ description: User banned successfully
content:
application/json:
schema:
- type: object
- properties:
- email_password:
- type: object
- properties:
- enabled:
- type: boolean
- method:
- type: string
- name:
- type: string
- anonymous:
- type: object
- properties:
- enabled:
- type: boolean
- method:
- type: string
- name:
- type: string
- oauth_providers:
- type: array
- items:
- type: object
- properties:
- enabled:
- type: boolean
- method:
- type: string
- provider:
- type: string
- name:
- type: string
- redirect_url:
- type: string
- scopes:
- type: array
- items:
- type: string
- available_methods:
- type: array
- items:
- type: string
- example:
- - email_password
- - oauth_google
- - oauth_github
- put:
+ $ref: '#/components/schemas/BanUserResponse'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/auth/users/{userId}/unban:
+ post:
tags:
- - Auth Configuration
- summary: Configure authentication methods (unified)
+ - Auth Admin
+ summary: Unban a user
description: |
- Configure all authentication methods in a single request.
- At least one method must remain enabled.
- operationId: configureAuthMethods
+ Removes a ban from a user, restoring their ability to sign in.
+ The user's status is set back to 'active'.
+ operationId: unbanAuthUser
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- content:
- application/json:
- schema:
- type: object
- properties:
- enable_email_password:
- type: boolean
- enable_anonymous:
- type: boolean
- oauth_providers:
- type: array
- items:
- type: object
- properties:
- provider:
- type: string
- enabled:
- type: boolean
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
responses:
'200':
- description: Methods configured
- '400':
- description: At least one method must be enabled
- /projects/{id}/oauth/configs:
+ description: User unbanned successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UnbanUserResponse'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/email-templates:
get:
tags:
- - OAuth Configuration
- summary: List OAuth configurations
- description: List all OAuth provider configurations for this project
- operationId: listOAuthConfigs
+ - Auth Configuration
+ summary: List email templates
+ description: Returns all custom email templates for this project.
+ operationId: listEmailTemplates
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
responses:
'200':
- description: List of OAuth configurations
+ description: Email templates list
content:
application/json:
schema:
type: object
properties:
- configs:
+ data:
type: array
items:
- $ref: '#/components/schemas/OAuthConfig'
+ $ref: '#/components/schemas/EmailTemplate'
post:
tags:
- - OAuth Configuration
- summary: Create OAuth configuration
- description: Configure OAuth provider (Google, GitHub, Microsoft, Apple, Device)
- operationId: createOAuthConfig
+ - Auth Configuration
+ summary: Create email template
+ description: |
+ Creates a custom email template for the project. Custom email templates
+ are a PRO-plan feature: requests from a FREE-plan project owner are
+ rejected with 403, and FREE projects always send the built-in default
+ templates regardless of any previously saved custom rows.
+ Every project is created with one template per type, so customizing one
+ is usually a PUT; creating a type the project already has returns 409.
+ Valid template types: welcome, confirmation, password_reset, password_changed
+ operationId: createEmailTemplate
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
requestBody:
@@ -9662,2799 +9882,4993 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateOAuthConfigRequest'
+ $ref: '#/components/schemas/CreateEmailTemplateRequest'
responses:
'201':
- description: OAuth config created
+ description: Template created
content:
application/json:
schema:
- $ref: '#/components/schemas/OAuthConfig'
+ $ref: '#/components/schemas/EmailTemplate'
+ '400':
+ description: Invalid template type
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Custom email templates require the PRO plan
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'409':
- description: Provider already configured
- /projects/{id}/oauth/configs/{provider}:
+ description: The project already has a template of this type
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/email-templates/{type}:
get:
tags:
- - OAuth Configuration
- summary: Get OAuth configuration
- operationId: getOAuthConfig
+ - Auth Configuration
+ summary: Get email template
+ operationId: getEmailTemplate
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: provider
+ - name: type
in: path
required: true
schema:
type: string
enum:
- - google
- - github
- - microsoft
- - apple
- - device
- - name: client_id
- in: query
- required: false
- schema:
- type: string
- description: Required when `provider=device` to select a specific device client.
+ - welcome
+ - confirmation
+ - password_reset
+ - password_changed
responses:
'200':
- description: OAuth configuration
+ description: Email template
content:
application/json:
schema:
- $ref: '#/components/schemas/OAuthConfig'
+ $ref: '#/components/schemas/EmailTemplate'
+ '404':
+ description: Template not found
put:
tags:
- - OAuth Configuration
- summary: Update OAuth configuration
- operationId: updateOAuthConfig
+ - Auth Configuration
+ summary: Update email template
+ description: |
+ Updates a custom email template. Custom email templates are a PRO-plan
+ feature: requests from a FREE-plan project owner are rejected with 403
+ (including after a PRO→FREE downgrade), so a FREE project cannot modify
+ templates and always sends the built-in defaults.
+ operationId: updateEmailTemplate
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: provider
+ - name: type
in: path
required: true
schema:
type: string
enum:
- - google
- - github
- - microsoft
- - apple
- - device
- - name: client_id
- in: query
- required: false
- schema:
- type: string
- description: Required when `provider=device` to select a specific device client.
+ - welcome
+ - confirmation
+ - password_reset
+ - password_changed
requestBody:
+ required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateOAuthConfigRequest'
+ $ref: '#/components/schemas/UpdateEmailTemplateRequest'
responses:
'200':
- description: OAuth config updated
+ description: Template updated
content:
application/json:
schema:
- $ref: '#/components/schemas/OAuthConfig'
- delete:
+ $ref: '#/components/schemas/EmailTemplate'
+ '403':
+ description: Custom email templates require the PRO plan
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Template not found
+ delete:
tags:
- - OAuth Configuration
- summary: Delete OAuth configuration
- operationId: deleteOAuthConfig
+ - Auth Configuration
+ summary: Delete email template
+ description: |
+ Deletes a custom template, reverting to the default. Custom email
+ templates are a PRO-plan feature: requests from a FREE-plan project owner
+ are rejected with 403.
+ operationId: deleteEmailTemplate
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: provider
+ - name: type
in: path
required: true
schema:
type: string
enum:
- - google
- - github
- - microsoft
- - apple
- - device
- - name: client_id
- in: query
- required: false
- schema:
- type: string
- description: Required when `provider=device` to select a specific device client.
+ - welcome
+ - confirmation
+ - password_reset
+ - password_changed
responses:
'204':
- description: OAuth config deleted
- /projects/{id}/oauth/providers:
+ description: Template deleted
+ '403':
+ description: Custom email templates require the PRO plan
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Template not found
+ /email-templates/defaults:
get:
tags:
- - OAuth Configuration
- summary: List available OAuth providers
- description: Get list of supported OAuth providers and their default scopes
- operationId: listAvailableOAuthProviders
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - Auth Configuration
+ summary: Get default email templates
+ description: Returns the default email templates used when no custom template is configured.
+ operationId: getDefaultEmailTemplates
responses:
'200':
- description: Available providers
+ description: Default templates
content:
application/json:
schema:
type: object
properties:
- providers:
+ data:
type: array
items:
- type: object
- properties:
- id:
- type: string
- name:
- type: string
- default_scopes:
- type: array
- items:
- type: string
- /auth/oauth/{provider}/authorize:
+ $ref: '#/components/schemas/EmailTemplate'
+ /email-templates/defaults/{type}:
get:
tags:
- - OAuth Authentication
- summary: Start OAuth authorization
- description: |
- Redirects user to OAuth provider for authorization.
- Handles CSRF protection with state parameter.
- Project is identified via the anon_key query parameter.
- operationId: authOAuthAuthorize
+ - Auth Configuration
+ summary: Get default email template by type
+ operationId: getDefaultEmailTemplate
parameters:
- - name: provider
+ - name: type
in: path
required: true
schema:
type: string
enum:
- - google
- - github
- - microsoft
- - apple
- - name: anon_key
- in: query
- required: true
- schema:
- type: string
- description: Project anon key (required - identifies the project)
- - name: redirect_url
- in: query
- schema:
- type: string
- description: |
- URL to redirect to after the OAuth flow (optional). Must exactly
- match an entry in the project's `allowed_redirect_urls`, including
- its query string, or be the project's own managed hosted-auth page
- URL.
- - name: client_state
- in: query
- schema:
- type: string
- maxLength: 255
- description: |
- Optional application nonce. It is stored with the server-generated
- provider state and echoed to redirect_url as `state`.
- - name: response_mode
- in: query
- schema:
- type: string
- enum:
- - code
- description: |
- Set to `code` to receive a short-lived authorization code at
- redirect_url, then use POST /auth/oauth/exchange to obtain the
- session. `redirect_url` is required in this mode. When omitted, the
- established session-fragment response is retained for compatibility
- with existing clients.
+ - welcome
+ - confirmation
+ - password_reset
+ - password_changed
responses:
- '307':
- description: Redirect to OAuth provider
- '400':
- description: |
- OAuth provider is disabled for this project, or `redirect_url` is
- not registered in `allowed_redirect_urls`
+ '200':
+ description: Default template
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EmailTemplate'
'404':
- description: OAuth provider not configured
- /auth/oauth/{provider}/callback:
+ description: Template type not found
+ /projects/{id}/auth/config:
get:
tags:
- - OAuth Authentication
- summary: OAuth callback handler
- description: |
- Handles OAuth provider callback with authorization code.
- Exchanges code for tokens and creates/signs in user.
- operationId: authOAuthCallback
+ - Auth Configuration
+ summary: Get auth configuration
+ operationId: getAuthConfig
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - name: provider
- in: path
- required: true
- schema:
- type: string
- enum:
- - google
- - github
- - microsoft
- - apple
- - name: code
- in: query
- required: true
- schema:
- type: string
- - name: state
- in: query
- required: true
- schema:
- type: string
- - name: error
- in: query
- schema:
- type: string
+ - $ref: '#/components/parameters/ProjectId'
responses:
'200':
- description: Existing user signed in (when redirect_url was omitted)
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AuthTokenResponse'
- '201':
- description: New user created and signed in (when redirect_url was omitted)
+ description: Auth configuration
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthTokenResponse'
- '303':
- description: |
- Redirect to the exact registered redirect_url. Flows that requested
- response_mode=code receive a short-lived, single-use `code` and
- optional application `state`; compatibility flows receive the
- established session fragment.
- '400':
- description: |
- Missing/invalid code or state, the state parameter expired, or the
- flow's stored redirect_url is no longer registered in
- allowed_redirect_urls (re-checked at callback time)
- '403':
- description: |
- The provider's email domain is not in `allowed_email_domains`. Creating
- an account is refused under `signup` and `signup_and_signin`; signing in
- an already-linked account is refused under `signup_and_signin`.
- '409':
- description: Email already exists (requires linking)
- /auth/oauth/exchange:
- post:
+ $ref: '#/components/schemas/AuthConfig'
+ put:
tags:
- - OAuth Authentication
- summary: Exchange OAuth authorization code
+ - Auth Configuration
+ summary: Update auth configuration
description: |
- Atomically consumes a short-lived callback code and returns the user's
- session. The request must use the same project anon key and exact
- redirect_url that initiated the flow.
- operationId: authOAuthExchange
+ Updates the project's auth configuration. Only the fields present in
+ the body are changed.
+ operationId: updateAuthConfig
security:
- - AnonKey: []
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
requestBody:
- required: true
content:
application/json:
schema:
- type: object
- required:
- - code
- - redirect_url
- properties:
- code:
- type: string
- redirect_url:
- type: string
- format: uri
+ $ref: '#/components/schemas/UpdateAuthConfigRequest'
responses:
'200':
- description: Authorization code consumed and session created
+ description: Configuration updated
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthTokenResponse'
- '400':
- description: Invalid, expired, consumed, or redirect-mismatched code
- '401':
- description: Missing or invalid project anon key
+ $ref: '#/components/schemas/AuthConfig'
'403':
description: |
- Email confirmation is now required, or the account's email domain is not
- in `allowed_email_domains` while `allowed_email_domains_mode` is
- `signup_and_signin`. Both are re-checked here because the code outlives
- the callback that issued it.
- '429':
- description: Too many exchange attempts from this client
- /auth/device/authorize:
+ The update would turn on, widen, or otherwise edit the email domain
+ allowlist (`allowed_email_domains`, `allowed_email_domains_mode`)
+ for a project that is not on the PRO plan. A FREE project keeps
+ whatever allowlist it already has — parked, enforcing nothing until
+ it upgrades — and may still remove it, so a downgrade never leaves a
+ project locked out of its own signups.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/auth/config/test-email:
post:
tags:
- - OAuth Authentication
- summary: Start RFC8628 device authorization
+ - Auth Configuration
+ summary: Send a test email using the project's saved SMTP config
description: |
- Starts OAuth 2.0 Device Authorization Grant (RFC 8628).
- Returns `device_code` for the CLI and `user_code` for browser verification.
-
- By default the returned `verification_uri` / `verification_uri_complete`
- point at the project's managed device-approval page served by this API
- (`/projects/{projectId}/auth/hosted?action=device&user_code=...&anon_key=...`),
- which requires managed auth enabled and a default anon key for the
- project.
-
- Projects can override this by setting `device_verification_url` on the
- auth config (`PATCH /auth/config`). When set, that URL is returned as-is
- with the `user_code` appended (no `action=device` hint and no embedded
- anon key — the page brings its own), so a CLI's `login` command surfaces
- the project's own RFC 8628 approval page. With a custom URL, device login
- does **not** require managed auth to be enabled; the custom page's origin
- must be in the project's auth CORS allowlist to call
- `POST /auth/device/verify`. Either way the verification page must
- authenticate the end user and call `POST /auth/device/verify` with the
- `user_code`. See the device-auth guide for both approaches.
- operationId: authDeviceAuthorize
+ Sends a diagnostic email to `to_email` using the project's
+ persisted `auth_config` SMTP credentials. If `html_body` or
+ `text_body` is supplied, the override path is taken: those
+ values (plus optional `subject`) are rendered through
+ html/text templates against the project's `Data` and
+ used as the body — used by the template editor's "Send Test"
+ affordance to preview an unsaved template. With both bodies
+ omitted, a hardcoded diagnostic message is sent and any
+ `subject` field is ignored. Sending `subject` alone (no
+ bodies) is rejected with 400 to avoid a silently-dropped
+ subject or a blank message. Also rejects with 400 if
+ `email_enabled=false` or `smtp_host` is empty.
+ operationId: testEmailConfig
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - client_id
- properties:
- client_id:
- type: string
- description: Enabled `device` OAuth client ID for the target project
+ $ref: '#/components/schemas/TestEmailRequest'
responses:
'200':
- description: Device authorization started
+ description: Test email sent
content:
application/json:
schema:
- $ref: '#/components/schemas/DeviceAuthorizationResponse'
+ $ref: '#/components/schemas/TestEmailResponse'
'400':
- description: Invalid request or unauthorized client
+ description: Invalid request or email delivery not configured
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Project not found
+ '502':
+ description: Template render failure or SMTP delivery failed
+ /projects/{id}/auth/hosted-pages/{pageType}:
+ get:
+ tags:
+ - Auth Configuration
+ summary: Get hosted auth page
+ description: |
+ Returns the saved HTML/CSS for the page type, or `page: null` when the
+ project has not customized it yet. Always returns `defaults` (the theme
+ shell to seed an editor with, which is valid input to the update endpoint)
+ and `runtime` (the script the rendered page runs, plus a preview harness).
+ operationId: getAuthHostedPage
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: pageType
+ in: path
+ required: true
+ schema:
+ $ref: '#/components/schemas/HostedAuthPageType'
+ responses:
+ '200':
+ description: Hosted page loaded
content:
application/json:
schema:
- $ref: '#/components/schemas/OAuthErrorResponse'
- /auth/device/token:
- post:
+ $ref: '#/components/schemas/AuthHostedPageResponse'
+ put:
tags:
- - OAuth Authentication
- summary: Poll device token endpoint
+ - Auth Configuration
+ summary: Update hosted auth page
description: |
- RFC8628 token polling endpoint.
- Returns OAuth errors such as `authorization_pending`, `slow_down`, `access_denied`, and `expired_token`.
- operationId: authDeviceToken
+ Saves the current HTML/CSS for this page type.
+ Security validation rejects script tags, javascript: URLs, inline event handlers, iframe/object/embed/meta/link tags in HTML,
+ and closing style/head tags in CSS.
+ operationId: updateAuthHostedPage
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: pageType
+ in: path
+ required: true
+ schema:
+ $ref: '#/components/schemas/HostedAuthPageType'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - grant_type
- - device_code
- - client_id
- properties:
- grant_type:
- type: string
- enum:
- - urn:ietf:params:oauth:grant-type:device_code
- device_code:
- type: string
- client_id:
- type: string
+ $ref: '#/components/schemas/UpdateAuthHostedPageRequest'
responses:
'200':
- description: Device flow completed, auth-user session minted
+ description: Hosted page updated
content:
application/json:
schema:
- $ref: '#/components/schemas/AuthTokenResponse'
+ $ref: '#/components/schemas/AuthHostedPageResponse'
'400':
- description: Polling state/error response
+ description: Invalid input or unsafe markup
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Project not found
+ /projects/{id}/auth/pages/appearance:
+ get:
+ tags:
+ - Auth Configuration
+ summary: Get managed auth page appearance
+ operationId: getAuthPageAppearance
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '200':
+ description: Saved appearance and effective plan state
content:
application/json:
schema:
- $ref: '#/components/schemas/OAuthErrorResponse'
+ $ref: '#/components/schemas/AuthPageAppearanceResponse'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: |
- `access_denied` - the approving account's email domain is not in
- `allowed_email_domains` while `allowed_email_domains_mode` is
- `signup_and_signin`. Re-checked here because approval and redemption
- are separate requests.
+ description: Access denied
content:
application/json:
schema:
- $ref: '#/components/schemas/OAuthErrorResponse'
- /auth/device/verify:
- post:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Appearance could not be read
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/auth/pages/theme:
+ put:
tags:
- - OAuth Authentication
- summary: Approve or deny a device code
- description: |
- Browser-side endpoint for authenticated auth-users to approve (`approve`) or deny (`deny`) a `user_code`.
-
- Called by the verification page after the end user signs in. The grant is
- scoped to the project the auth-user token belongs to: approving a
- `user_code` issued for a different project returns `403`. This endpoint
- does not require managed auth to be enabled, so a custom verification page
- (hosted anywhere) can drive approval — it just needs an authenticated
- project auth-user access token and, for cross-origin browser calls, the
- page origin allowed in the project's auth CORS settings.
- operationId: authDeviceVerify
+ - Auth Configuration
+ summary: Save the managed auth page theme
+ operationId: updateAuthPageTheme
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - user_code
- properties:
- user_code:
- type: string
- action:
- type: string
- enum:
- - approve
- - deny
- default: approve
+ $ref: '#/components/schemas/UpdateAuthPageThemeRequest'
responses:
'200':
- description: Verification action accepted
+ description: Theme saved
content:
application/json:
schema:
- type: object
- properties:
- success:
- type: boolean
- status:
- type: string
+ $ref: '#/components/schemas/UpdateAuthPageThemeRequest'
+ '400':
+ description: Invalid or unreadable theme
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'401':
- description: Not authenticated
+ description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/platform/exchange:
- post:
- tags:
- - OAuth Authentication
- summary: Exchange auth-user device session for platform token
- description: |
- Exchanges a verified auth-user device-flow session into a platform token for CLI usage.
- The target platform user is derived from authenticated auth-user mapping; client cannot select another user.
- operationId: authPlatformExchange
- security:
- - AuthUserAccessToken: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - client_id
- properties:
- client_id:
- type: string
- responses:
- '200':
- description: Platform token minted
+ '403':
+ description: Plan does not permit customisation
content:
application/json:
schema:
- $ref: '#/components/schemas/PlatformExchangeResponse'
- '403':
- description: |
- Exchange not allowed for this session/client/project, or the
- account's email domain is not in `allowed_email_domains` while
- `allowed_email_domains_mode` is `signup_and_signin`. The domain is
- re-checked here because the minted platform token outlives the
- session it is exchanged from.
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/oauth/providers:
- get:
+ '500':
+ description: Theme could not be saved
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ delete:
tags:
- - OAuth Authentication
- summary: List user's linked providers
- description: Get list of OAuth providers linked to current user
- operationId: authListOAuthProviders
+ - Auth Configuration
+ summary: Clear the managed auth page theme
+ operationId: deleteAuthPageTheme
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
responses:
- '200':
- description: Linked providers
+ '204':
+ description: Theme cleared
+ '401':
+ description: Unauthorized
content:
application/json:
schema:
- type: object
- properties:
- providers:
- type: array
- items:
- type: object
- properties:
- provider:
- type: string
- linked_at:
- type: string
- format: date-time
- updated_at:
- type: string
- format: date-time
- '401':
- description: Not authenticated
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Plan does not permit customisation
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/oauth/{provider}/link:
- post:
+ '404':
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '500':
+ description: Theme could not be cleared
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/auth/pages/{pageType}/layout:
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: pageType
+ in: path
+ required: true
+ schema:
+ $ref: '#/components/schemas/HostedAuthPageType'
+ put:
tags:
- - OAuth Authentication
- summary: Link OAuth provider to current user
- description: |
- Generates authorization URL to link OAuth provider to existing account.
- User must be authenticated.
- operationId: authLinkOAuthProvider
+ - Auth Configuration
+ summary: Save one managed auth page layout
+ operationId: updateAuthPageLayout
security:
- - AuthUserAccessToken: []
- parameters:
- - name: provider
- in: path
- required: true
- schema:
- type: string
- enum:
- - google
- - github
- - microsoft
- - apple
- - name: redirect_url
- in: query
- schema:
- type: string
- description: |
- URL to redirect to after linking completes (optional). Same
- allowed_redirect_urls requirement as GET /auth/oauth/{provider}/authorize.
- - name: client_state
- in: query
- schema:
- type: string
- maxLength: 255
- description: |
- Optional application nonce echoed to redirect_url as `state`.
- - name: response_mode
- in: query
- schema:
- type: string
- enum:
- - code
- description: |
- Set to `code` to receive a short-lived authorization code at
- redirect_url. `redirect_url` is required in this mode. When
- omitted, the established session-fragment response is retained for
- compatibility with existing clients.
+ - UserToken: []
+ - ProjectAccessToken: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateAuthPageLayoutRequest'
responses:
'200':
- description: Authorization URL generated
+ description: Layout saved
content:
application/json:
schema:
- type: object
- properties:
- authorization_url:
- type: string
+ $ref: '#/components/schemas/UpdateAuthPageLayoutRequest'
'400':
- description: redirect_url is not registered in allowed_redirect_urls
+ description: Invalid page type or layout
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Not authenticated
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Plan does not permit customisation
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: OAuth provider not configured
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: Provider already linked
+ '500':
+ description: Layout could not be saved
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/oauth/{provider}/unlink:
delete:
tags:
- - OAuth Authentication
- summary: Unlink OAuth provider
- description: |
- Remove OAuth provider from user's account.
- Cannot unlink if it's the only authentication method.
- operationId: authUnlinkOAuthProvider
+ - Auth Configuration
+ summary: Clear one managed auth page layout
+ operationId: deleteAuthPageLayout
security:
- - AuthUserAccessToken: []
- parameters:
- - name: provider
- in: path
- required: true
- schema:
- type: string
- enum:
- - google
- - github
- - microsoft
- - apple
+ - UserToken: []
+ - ProjectAccessToken: []
responses:
'204':
- description: Provider unlinked
+ description: Layout cleared
'400':
- description: Cannot unlink last authentication method
+ description: Invalid page type
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Not authenticated
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Plan does not permit customisation
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
- description: Provider not linked
+ description: Project not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /auth/oauth/{provider}/refresh-token:
- post:
+ '500':
+ description: Layout could not be cleared
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/auth/pages/{pageType}/preview:
+ get:
tags:
- - OAuth Authentication
- summary: Refresh OAuth provider token
+ - Auth Configuration
+ summary: Render a short-lived managed auth page preview
description: |
- Refresh the access token for an OAuth provider using its refresh token.
- Allows calling provider APIs on user's behalf (e.g., Google Drive, GitHub repos).
- operationId: refreshOAuthProviderToken
- security:
- - AuthUserAccessToken: []
+ Public HTML endpoint for a preview URL returned by the POST operation.
+ The signed ticket contains the unsaved appearance, expires shortly, and
+ runs the production page runtime against mocked authentication responses.
+ operationId: renderAuthPagePreview
+ security: []
parameters:
- - name: provider
+ - $ref: '#/components/parameters/ProjectId'
+ - name: pageType
in: path
required: true
+ schema:
+ $ref: '#/components/schemas/HostedAuthPageType'
+ - name: ticket
+ in: query
+ required: true
schema:
type: string
- enum:
- - google
- - github
- - microsoft
- - apple
+ minLength: 1
+ maxLength: 4096
+ description: Short-lived signed preview ticket returned by the POST operation.
responses:
'200':
- description: Token refreshed successfully
+ description: Rendered preview document
content:
- application/json:
+ text/html:
schema:
- type: object
- properties:
- message:
- type: string
- provider:
- type: string
- expires_in:
- type: integer
- '400':
- description: No refresh token available or refresh failed
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Not authenticated
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
+ type: string
'404':
- description: Provider not linked
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /auth/oauth/{provider}/token:
- get:
- tags:
- - OAuth Authentication
- summary: Get current provider access token
- description: |
- Get valid access token for OAuth provider.
- Automatically refreshes if expired.
- operationId: getOAuthProviderToken
- security:
- - AuthUserAccessToken: []
- parameters:
- - name: provider
- in: path
- required: true
- schema:
- type: string
- enum:
- - google
- - github
- - microsoft
- - apple
- responses:
- '200':
- description: Current access token
- content:
- application/json:
- schema:
- type: object
- properties:
- message:
- type: string
- provider:
- type: string
- expires_in:
- type: integer
- '401':
- description: Not authenticated
+ description: Ticket is invalid or expired, its path does not match, or managed authentication is disabled
content:
- application/json:
+ text/plain:
schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Provider not linked
+ type: string
+ '500':
+ description: Preview could not be rendered
content:
- application/json:
+ text/plain:
schema:
- $ref: '#/components/schemas/Error'
- /auth/oauth/{provider}/call-api:
+ type: string
post:
tags:
- - OAuth Authentication
- summary: Call OAuth provider API
- description: |
- Make an authenticated request to an OAuth provider's API on behalf of the user.
- The user's stored access token is automatically used and refreshed if needed.
-
- The request is always sent to the provider's fixed API base URL joined with
- the caller-supplied `endpoint`. `endpoint` must be a relative path beginning
- with `/` (optionally with a query string); it cannot change the target host.
- Absolute URLs, protocol-relative `//host` values, or userinfo (`@host`) are
- rejected with `400` so the request can never be redirected to another host.
-
- Examples of `endpoint`:
- - Google userinfo: `/oauth2/v1/userinfo`
- - GitHub repositories: `/user/repos`
- - Microsoft Graph profile: `/me`
-
- The response wraps the provider's raw JSON value with request metadata.
- An empty provider body is represented as `data: null`; the envelope
- preserves the provider's HTTP status in `status_code`, including errors.
- Provider response bodies are limited to 8 MiB after decompression.
- Transport failures, invalid JSON (including invalid UTF-8), and oversized
- bodies return `502`. Provider redirects to another origin are blocked and
- return `400`.
- operationId: callOAuthProviderAPI
+ - Auth Configuration
+ summary: Preview an unsaved managed auth page appearance
+ operationId: previewAuthPage
security:
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - name: provider
+ - $ref: '#/components/parameters/ProjectId'
+ - name: pageType
in: path
required: true
schema:
- type: string
- enum:
- - google
- - github
- - microsoft
- - apple
+ $ref: '#/components/schemas/HostedAuthPageType'
requestBody:
required: true
content:
application/json:
schema:
- type: object
- required:
- - endpoint
- properties:
- endpoint:
- type: string
- description: |
- Relative path on the provider's API, beginning with `/`. It is
- joined with the provider's fixed base URL; it must not contain a
- scheme, host, userinfo, or a leading `//`.
- example: /user/repos
- method:
- type: string
- enum:
- - GET
- - POST
- default: GET
- description: HTTP method to use
- body:
- type: object
- additionalProperties: true
- description: Request body for POST requests
+ $ref: '#/components/schemas/PreviewAuthPageRequest'
responses:
'200':
- description: Provider API response
+ description: Short-lived URL for the rendered preview document
content:
application/json:
schema:
- type: object
- description: OAuth provider API response envelope
- required:
- - provider
- - endpoint
- - status_code
- - data
- properties:
- provider:
- type: string
- enum:
- - google
- - github
- - microsoft
- - apple
- endpoint:
- type: string
- status_code:
- type: integer
- minimum: 100
- maximum: 599
- data:
- description: Raw provider JSON value, or null when the provider returns no body
- nullable: true
+ $ref: '#/components/schemas/PreviewAuthPageResponse'
'400':
- description: |
- Invalid request (for example: missing `endpoint`, an `endpoint` that is
- not a relative path, or an unsupported HTTP method), or a provider
- redirect to another origin.
+ description: Invalid page type or draft
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Not authenticated
+ description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '404':
- description: OAuth provider configuration not found
+ '403':
+ description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '500':
- description: Failed to create the provider API request
+ '404':
+ description: Project not found or managed authentication is disabled
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '502':
- description: Provider transport failure, invalid JSON, or response body larger than 8 MiB
+ '500':
+ description: Preview could not be rendered
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/anon-keys:
+ /projects/{id}/auth/hosted/{pageType}:
get:
tags:
- - Anon Keys
- summary: List anon keys
+ - Auth Configuration
+ summary: Render a managed auth page
description: |
- Supports two mutually exclusive pagination modes. Offset mode uses `page`
- and `limit` and is the default when neither `cursor` nor `search` is
- supplied (first page, default `limit`). Cursor mode uses `cursor` and
- `limit`, supports `search` (case-insensitive name match), and returns
- `next_cursor`. Sending both `page` and `cursor` (or `page` and `search`)
- returns 400.
- operationId: listAnonKeys
- security:
- - UserToken: []
+ Public HTML endpoint for signup, forgot-password, device approval,
+ verify-email, and reset-password pages. Login uses the path without a
+ page type.
+ Requires `Accept: text/html`.
+ Returns 404 when managed hosted pages are disabled for the project.
+ security: []
+ operationId: renderManagedAuthPage
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
+ - name: pageType
+ in: path
+ required: true
+ schema:
+ $ref: '#/components/schemas/HostedRenderablePageType'
responses:
'200':
- description: List of anon keys
+ description: Hosted auth page HTML
content:
- application/json:
+ text/html:
schema:
- type: object
- properties:
- data:
- type: array
- items:
- $ref: '#/components/schemas/AnonKey'
- total:
- type: integer
- description: Total number of items matching the query (so the UI can render numbered pages).
- has_more:
- type: boolean
- description: Whether a next page exists.
- next_cursor:
- type: string
- description: Opaque cursor for the next page (cursor pagination only)
- prev_cursor:
- type: string
- description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
- post:
+ type: string
+ '400':
+ description: Invalid project id or unsupported Accept header
+ '404':
+ description: Managed pages disabled or page type not found
+ /projects/{id}/auth/hosted:
+ get:
tags:
- - Anon Keys
- summary: Create anon key
- operationId: createAnonKey
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required:
- - name
- properties:
- name:
- type: string
- description: |
- Key name for identification.
- Can only contain letters, numbers, underscores, and hyphens.
- pattern: ^[A-Za-z0-9_-]+$
- minLength: 1
- maxLength: 255
- example: frontend-app
- permissions:
- type: array
- items:
- type: string
- enum:
- - auth.signup
- - auth.signin
- - auth.refresh
- - auth.logout
- - auth.password_reset
- - auth.confirm_email
- - auth.resend_confirmation
- - storage.upload
- - storage.download
- - storage.list
- - storage.delete
- - realtime.connect
- - realtime.subscribe
- - realtime.publish
- - functions.invoke
- description: |
- Optional list of permissions for this key.
- If not provided, defaults to auth-only permissions: auth.signup, auth.signin, auth.refresh, auth.logout, auth.password_reset, auth.confirm_email, auth.resend_confirmation.
- Storage, realtime, and functions permissions must be explicitly added if needed.
- example:
- - auth.signup
- - auth.signin
- - auth.refresh
- - auth.logout
- responses:
- '201':
- description: Anon key created
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AnonKey'
- /projects/{id}/anon-keys/{keyId}:
- get:
- tags:
- - Anon Keys
- summary: Get anon key
- description: Get details of a specific anon key
- operationId: getAnonKey
- security:
- - UserToken: []
+ - Auth Configuration
+ summary: Render default managed auth page
+ description: |
+ Public HTML endpoint for the managed login page.
+ Requires `Accept: text/html`.
+ security: []
+ operationId: renderDefaultManagedAuthPage
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: keyId
- in: path
- required: true
+ - name: action
+ in: query
+ required: false
schema:
type: string
- format: uuid
- responses:
- '200':
- description: Anon key details
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AnonKey'
- '404':
- description: Key not found
- delete:
- tags:
- - Anon Keys
- summary: Revoke anon key
- description: Revokes key - it will immediately stop working
- operationId: revokeAnonKey
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: keyId
- in: path
- required: true
+ enum:
+ - login
+ - signup
+ - forgot-password
+ - device
+ description: |
+ Optional deep-link action for the unified hosted page. Ignored when a
+ custom login page is configured. `action=device` renders the device
+ authorization approval UI inline (no redirect to any external app);
+ it signs the user in and calls `POST /auth/device/verify`.
+ - name: user_code
+ in: query
+ required: false
schema:
type: string
- format: uuid
+ description: Device user code (from `POST /auth/device/authorize`) used with `action=device`.
+ - name: anon_key
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Project anon key used by built-in managed auth flows (required for login/signup/device actions).
+ - name: state
+ in: query
+ required: false
+ schema:
+ type: string
+ description: |
+ Opaque one-time nonce generated by the client SDK before redirecting
+ here. On successful login/signup it is echoed back in the post-auth
+ redirect fragment as `state`, so the SDK can bind the returned session
+ to the flow it initiated (login-CSRF / session-fixation defense). The
+ SDK rejects a returned session whose `state` does not match.
responses:
- '204':
- description: Key revoked
- '401':
- description: Unauthorized
- '403':
- description: Forbidden
- '404':
- description: Key not found
- '409':
- description: Cannot delete the project's default anon key
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '500':
- description: Internal server error
+ '200':
+ description: Hosted auth page HTML
content:
- application/json:
+ text/html:
schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/anon-keys/{keyId}/regenerate:
- post:
+ type: string
+ '400':
+ description: Invalid project id or unsupported Accept header
+ '404':
+ description: Managed pages disabled
+ /projects/{id}/auth/hosted/login/options:
+ get:
tags:
- - Anon Keys
- summary: Regenerate anon key
- description: Generate new JWT value for existing key
- operationId: regenerateAnonKey
- security:
- - UserToken: []
+ - Auth Configuration
+ summary: Get hosted login runtime options
+ description: |
+ Returns runtime options for the built-in managed login flow.
+ Requires `anon_key` query parameter.
+ Rate limited per project and client IP. Excess requests return `429` and `Retry-After`.
+ security: []
+ operationId: getHostedLoginOptions
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: keyId
- in: path
+ - name: anon_key
+ in: query
required: true
schema:
type: string
- format: uuid
responses:
'200':
- description: Key regenerated
+ description: Hosted login options returned
content:
application/json:
schema:
- $ref: '#/components/schemas/AnonKey'
- /projects/{id}/anon-keys/{keyId}/set-default:
+ $ref: '#/components/schemas/HostedLoginOptionsResponse'
+ '401':
+ description: Invalid or missing anon key
+ '404':
+ description: Managed pages disabled
+ '429':
+ description: Rate limit exceeded
+ /projects/{id}/auth/hosted/login/check-email:
post:
tags:
- - Anon Keys
- summary: Set default anon key
- description: Promotes the given key to the project's configured default. At most one key per project can be default.
- operationId: setDefaultAnonKey
- security:
- - UserToken: []
+ - Auth Configuration
+ summary: Check whether email exists for hosted login flow
+ description: |
+ Used by the built-in managed login page to branch UI between signin and signup.
+ Requires anon key in Authorization header.
+ Rate limited per project and client IP. Excess requests return `429` and `Retry-After`.
+ security: []
+ operationId: hostedLoginCheckEmail
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: keyId
- in: path
+ - name: Authorization
+ in: header
required: true
schema:
type: string
- format: uuid
+ description: Bearer anon key (`Bearer `)
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HostedLoginEmailCheckRequest'
responses:
'200':
- description: Key set as default
+ description: Email existence evaluated
content:
application/json:
schema:
- $ref: '#/components/schemas/AnonKey'
+ $ref: '#/components/schemas/HostedLoginEmailCheckResponse'
'401':
- description: Unauthorized
- '403':
- description: Forbidden
- '404':
- description: Anon key not found
- '500':
- description: Internal server error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- /projects/{id}/service-keys:
+ description: Invalid or missing anon key
+ '429':
+ description: Rate limit exceeded
+ /projects/{id}/auth/methods:
get:
tags:
- - Service Keys
- summary: List service keys (paginated)
+ - Auth Configuration
+ summary: Get all authentication methods
description: |
- List all service role keys for a project with pagination.
-
- **WARNING:** Service keys bypass RLS - for backend/admin use only!
- operationId: listServiceKeys
+ Returns all configured authentication methods for this project,
+ including email/password, anonymous, device authorization, and OAuth providers.
+ operationId: getAuthMethods
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Page'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
responses:
'200':
- description: Paginated list of service keys
+ description: Authentication methods configuration
content:
application/json:
schema:
- $ref: '#/components/schemas/PaginatedServiceKeys'
- post:
+ type: object
+ properties:
+ email_password:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ method:
+ type: string
+ name:
+ type: string
+ anonymous:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ method:
+ type: string
+ name:
+ type: string
+ oauth_providers:
+ type: array
+ items:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ method:
+ type: string
+ provider:
+ type: string
+ name:
+ type: string
+ redirect_url:
+ type: string
+ scopes:
+ type: array
+ items:
+ type: string
+ available_methods:
+ type: array
+ items:
+ type: string
+ example:
+ - email_password
+ - oauth_google
+ - oauth_github
+ put:
tags:
- - Service Keys
- summary: Create service key
+ - Auth Configuration
+ summary: Configure authentication methods (unified)
description: |
- Create a new service role key for admin operations.
-
- **WARNING:** Service keys bypass all RLS policies!
- Store securely and NEVER expose in frontend code.
- operationId: createServiceKey
+ Configure all authentication methods in a single request.
+ At least one method must remain enabled.
+ operationId: configureAuthMethods
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
requestBody:
- required: true
content:
application/json:
schema:
type: object
- required:
- - name
properties:
- name:
- type: string
- description: |
- Descriptive name for the key (e.g., "admin-dashboard", "background-jobs").
- Can only contain letters, numbers, underscores, and hyphens.
- pattern: ^[A-Za-z0-9_-]+$
- minLength: 1
- maxLength: 255
- example: admin-dashboard
- permissions:
+ enable_email_password:
+ type: boolean
+ enable_anonymous:
+ type: boolean
+ oauth_providers:
type: array
items:
- type: string
- description: |
- Optional least-privilege scope for the key. When omitted, empty, or
- containing only blank strings, the key is granted full access (["*"])
- for backward compatibility. Provide an explicit list (e.g.
- ["functions.invoke", "locks.manage"]) to restrict the key; "*"
- grants everything. Scope enforcement applies to function invocation,
- storage object operations, and project locks.
- example:
- - functions.invoke
- - locks.manage
+ type: object
+ properties:
+ provider:
+ type: string
+ enabled:
+ type: boolean
responses:
- '201':
- description: Service key created - save the key_value immediately!
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ServiceKey'
- '409':
- description: Key with this name already exists
- /projects/{id}/service-keys/{keyId}:
+ '200':
+ description: Methods configured
+ '400':
+ description: At least one method must be enabled
+ /projects/{id}/oauth/configs:
get:
tags:
- - Service Keys
- summary: Get service key
- description: Get details of a specific service key
- operationId: getServiceKey
+ - OAuth Configuration
+ summary: List OAuth configurations
+ description: List all OAuth provider configurations for this project
+ operationId: listOAuthConfigs
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: keyId
- in: path
- required: true
- schema:
- type: string
- format: uuid
responses:
'200':
- description: Service key details
+ description: List of OAuth configurations
content:
application/json:
schema:
- $ref: '#/components/schemas/ServiceKey'
- '404':
- description: Key not found
- delete:
+ type: object
+ properties:
+ configs:
+ type: array
+ items:
+ $ref: '#/components/schemas/OAuthConfig'
+ post:
tags:
- - Service Keys
- summary: Delete service key
- description: |
- Permanently delete a service key.
- Any services using this key will immediately lose access.
- operationId: deleteServiceKey
+ - OAuth Configuration
+ summary: Create OAuth configuration
+ description: Configure OAuth provider (Google, GitHub, Microsoft, Apple, Device)
+ operationId: createOAuthConfig
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: keyId
- in: path
- required: true
- schema:
- type: string
- format: uuid
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateOAuthConfigRequest'
responses:
- '204':
- description: Key deleted
- /projects/{id}/service-keys/{keyId}/regenerate:
- post:
+ '201':
+ description: OAuth config created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OAuthConfig'
+ '409':
+ description: Provider already configured
+ /projects/{id}/oauth/configs/{provider}:
+ get:
tags:
- - Service Keys
- summary: Regenerate service key
- description: |
- Generate new JWT value for existing key.
- The old key is immediately invalidated.
- Update your backend services with the new key before regenerating in production.
- operationId: regenerateServiceKey
+ - OAuth Configuration
+ summary: Get OAuth configuration
+ operationId: getOAuthConfig
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - name: keyId
+ - name: provider
in: path
required: true
schema:
type: string
- format: uuid
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ - device
+ - name: client_id
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Required when `provider=device` to select a specific device client.
responses:
'200':
- description: Key regenerated - save the new key_value immediately!
+ description: OAuth configuration
content:
application/json:
schema:
- $ref: '#/components/schemas/ServiceKey'
- /projects/{id}/storage/buckets:
- get:
+ $ref: '#/components/schemas/OAuthConfig'
+ put:
tags:
- - Storage Buckets
- summary: List all storage buckets in a project
- description: |
- With no pagination params, returns the full bucket list as a bare array
- (legacy). Supplying `cursor`, `ending_before`, `search`, or `limit`
- switches to keyset (cursor) pagination and returns a paginated envelope
- with `next_cursor`/`prev_cursor` and a filtered `total`.
- operationId: listStorageBuckets
+ - OAuth Configuration
+ summary: Update OAuth configuration
+ operationId: updateOAuthConfig
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/Limit'
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ - device
+ - name: client_id
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Required when `provider=device` to select a specific device client.
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateOAuthConfigRequest'
responses:
'200':
- description: |
- Either the full bucket list (bare array, legacy) or a paginated
- envelope when cursor pagination is requested.
+ description: OAuth config updated
content:
application/json:
schema:
- oneOf:
- - type: array
- items:
- $ref: '#/components/schemas/StorageBucket'
- - $ref: '#/components/schemas/PaginatedStorageBuckets'
- post:
+ $ref: '#/components/schemas/OAuthConfig'
+ delete:
tags:
- - Storage Buckets
- summary: Create a new storage bucket
- operationId: createStorageBucket
+ - OAuth Configuration
+ summary: Delete OAuth configuration
+ operationId: deleteOAuthConfig
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/CreateStorageBucketRequest'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ - device
+ - name: client_id
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Required when `provider=device` to select a specific device client.
responses:
- '201':
- description: Bucket created
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StorageBucket'
- '409':
- description: Bucket already exists
- /projects/{id}/storage/buckets/{bucketName}:
+ '204':
+ description: OAuth config deleted
+ /projects/{id}/oauth/providers:
get:
tags:
- - Storage Buckets
- summary: Get storage bucket by name
- operationId: getStorageBucket
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/BucketName'
- responses:
- '200':
- description: Bucket details
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StorageBucket'
- '404':
- description: Bucket not found
- patch:
- tags:
- - Storage Buckets
- summary: Update storage bucket settings
- operationId: updateStorageBucket
+ - OAuth Configuration
+ summary: List available OAuth providers
+ description: Get list of supported OAuth providers and their default scopes
+ operationId: listAvailableOAuthProviders
security:
- UserToken: []
+ - ProjectAccessToken: []
parameters:
- $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/BucketName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/UpdateStorageBucketRequest'
responses:
'200':
- description: Bucket updated
+ description: Available providers
content:
application/json:
schema:
- $ref: '#/components/schemas/StorageBucket'
- delete:
+ type: object
+ properties:
+ providers:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ name:
+ type: string
+ default_scopes:
+ type: array
+ items:
+ type: string
+ /auth/oauth/{provider}/authorize:
+ get:
tags:
- - Storage Buckets
- summary: Delete storage bucket and all objects
- operationId: deleteStorageBucket
- security:
- - UserToken: []
+ - OAuth Authentication
+ summary: Start OAuth authorization
+ description: |
+ Redirects user to OAuth provider for authorization.
+ Handles CSRF protection with state parameter.
+ Project is identified via the anon_key query parameter.
+ operationId: authOAuthAuthorize
parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/BucketName'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ - name: anon_key
+ in: query
+ required: true
+ schema:
+ type: string
+ description: Project anon key (required - identifies the project)
+ - name: redirect_url
+ in: query
+ schema:
+ type: string
+ description: |
+ URL to redirect to after the OAuth flow (optional). Must exactly
+ match an entry in the project's `allowed_redirect_urls`, including
+ its query string, or be the project's own managed hosted-auth page
+ URL.
+ - name: client_state
+ in: query
+ schema:
+ type: string
+ maxLength: 255
+ description: |
+ Optional application nonce. It is stored with the server-generated
+ provider state and echoed to redirect_url as `state`.
+ - name: response_mode
+ in: query
+ schema:
+ type: string
+ enum:
+ - code
+ description: |
+ Set to `code` to receive a short-lived authorization code at
+ redirect_url, then use POST /auth/oauth/exchange to obtain the
+ session. `redirect_url` is required in this mode. When omitted, the
+ established session-fragment response is retained for compatibility
+ with existing clients.
responses:
- '200':
- description: Bucket deleted
- /projects/{id}/storage/buckets/{bucketName}/policies:
+ '307':
+ description: Redirect to OAuth provider
+ '400':
+ description: |
+ OAuth provider is disabled for this project, or `redirect_url` is
+ not registered in `allowed_redirect_urls`
+ '404':
+ description: OAuth provider not configured
+ /auth/oauth/{provider}/callback:
get:
tags:
- - Storage Policies
- summary: List storage policies for a bucket
- operationId: listStoragePolicies
- security:
- - UserToken: []
+ - OAuth Authentication
+ summary: OAuth callback handler
+ description: |
+ Handles OAuth provider callback with authorization code.
+ Exchanges code for tokens and creates/signs in user.
+ operationId: authOAuthCallback
parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/BucketName'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ - name: code
+ in: query
+ required: true
+ schema:
+ type: string
+ - name: state
+ in: query
+ required: true
+ schema:
+ type: string
+ - name: error
+ in: query
+ schema:
+ type: string
responses:
'200':
- description: List of policies
+ description: Existing user signed in (when redirect_url was omitted)
content:
application/json:
schema:
- type: array
- items:
- $ref: '#/components/schemas/StoragePolicy'
+ $ref: '#/components/schemas/AuthTokenResponse'
+ '201':
+ description: New user created and signed in (when redirect_url was omitted)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthTokenResponse'
+ '303':
+ description: |
+ Redirect to the exact registered redirect_url. Flows that requested
+ response_mode=code receive a short-lived, single-use `code` and
+ optional application `state`; compatibility flows receive the
+ established session fragment.
+ '400':
+ description: |
+ Missing/invalid code or state, the state parameter expired, or the
+ flow's stored redirect_url is no longer registered in
+ allowed_redirect_urls (re-checked at callback time)
+ '403':
+ description: |
+ The provider's email domain is not in `allowed_email_domains`. Creating
+ an account is refused under `signup` and `signup_and_signin`; signing in
+ an already-linked account is refused under `signup_and_signin`.
+ '409':
+ description: Email already exists (requires linking)
+ /auth/oauth/exchange:
post:
tags:
- - Storage Policies
- summary: Create a storage policy
- operationId: createStoragePolicy
+ - OAuth Authentication
+ summary: Exchange OAuth authorization code
+ description: |
+ Atomically consumes a short-lived callback code and returns the user's
+ session. The request must use the same project anon key and exact
+ redirect_url that initiated the flow.
+ operationId: authOAuthExchange
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/BucketName'
+ - AnonKey: []
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/CreateStoragePolicyRequest'
- responses:
- '201':
- description: Policy created
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StoragePolicy'
- /projects/{id}/storage/buckets/{bucketName}/policies/{policyId}:
- delete:
- tags:
- - Storage Policies
- summary: Delete a storage policy
- operationId: deleteStoragePolicy
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - $ref: '#/components/parameters/BucketName'
- - name: policyId
- in: path
- required: true
- schema:
- type: string
- format: uuid
+ type: object
+ required:
+ - code
+ - redirect_url
+ properties:
+ code:
+ type: string
+ redirect_url:
+ type: string
+ format: uri
responses:
'200':
- description: Policy deleted
- /projects/{id}/storage/objects:
- get:
+ description: Authorization code consumed and session created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AuthTokenResponse'
+ '400':
+ description: Invalid, expired, consumed, or redirect-mismatched code
+ '401':
+ description: Missing or invalid project anon key
+ '403':
+ description: |
+ Email confirmation is now required, or the account's email domain is not
+ in `allowed_email_domains` while `allowed_email_domains_mode` is
+ `signup_and_signin`. Both are re-checked here because the code outlives
+ the callback that issued it.
+ '429':
+ description: Too many exchange attempts from this client
+ /auth/device/authorize:
+ post:
tags:
- - Storage Admin
- summary: List all storage objects in a project
+ - OAuth Authentication
+ summary: Start RFC8628 device authorization
description: |
- Returns a paginated list of all storage objects across all buckets in the project.
- Supports filtering by owner and pagination.
- operationId: listStorageObjectsAdmin
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- - name: owner_id
- in: query
- description: Filter by owner user ID
- schema:
- type: string
- format: uuid
- - name: page
- in: query
- description: Page number (1-based)
- schema:
- type: integer
- default: 1
- minimum: 1
- - name: limit
- in: query
- description: Items per page
- schema:
- type: integer
- default: 50
- minimum: 1
- maximum: 100
- - $ref: '#/components/parameters/Cursor'
- - $ref: '#/components/parameters/EndingBefore'
- - $ref: '#/components/parameters/Offset'
- - $ref: '#/components/parameters/Search'
+ Starts OAuth 2.0 Device Authorization Grant (RFC 8628).
+ Returns `device_code` for the CLI and `user_code` for browser verification.
+
+ By default the returned `verification_uri` / `verification_uri_complete`
+ point at the project's managed device-approval page served by this API
+ (`/projects/{projectId}/auth/hosted?action=device&user_code=...&anon_key=...`),
+ which requires managed auth enabled and a default anon key for the
+ project.
+
+ Projects can override this by setting `device_verification_url` on the
+ auth config (`PATCH /auth/config`). When set, that URL is returned as-is
+ with the `user_code` appended (no `action=device` hint and no embedded
+ anon key — the page brings its own), so a CLI's `login` command surfaces
+ the project's own RFC 8628 approval page. With a custom URL, device login
+ does **not** require managed auth to be enabled; the custom page's origin
+ must be in the project's auth CORS allowlist to call
+ `POST /auth/device/verify`. Either way the verification page must
+ authenticate the end user and call `POST /auth/device/verify` with the
+ `user_code`. See the device-auth guide for both approaches.
+ operationId: authDeviceAuthorize
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - client_id
+ properties:
+ client_id:
+ type: string
+ description: Enabled `device` OAuth client ID for the target project
responses:
'200':
- description: Paginated list of storage objects
+ description: Device authorization started
content:
application/json:
schema:
- type: object
- properties:
- data:
- type: array
- items:
- $ref: '#/components/schemas/StorageObjectWithBucket'
- page:
- type: integer
- limit:
- type: integer
- total:
- type: integer
- has_more:
- type: boolean
- next_cursor:
- type: string
- description: Opaque cursor for the next page (cursor pagination only)
- prev_cursor:
- type: string
- description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
- /projects/{id}/storage/stats:
- get:
- tags:
- - Storage Admin
- summary: Get storage statistics for a project
- description: Returns aggregate storage statistics including bucket count, object count, and total size.
- operationId: getStorageStats
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
- responses:
- '200':
- description: Storage statistics
+ $ref: '#/components/schemas/DeviceAuthorizationResponse'
+ '400':
+ description: Invalid request or unauthorized client
content:
application/json:
schema:
- $ref: '#/components/schemas/StorageStats'
- /projects/{id}/realtime/config:
- get:
+ $ref: '#/components/schemas/OAuthErrorResponse'
+ /auth/device/token:
+ post:
tags:
- - Realtime
- summary: Get realtime configuration for a project
- description: Returns the realtime configuration including enabled features and limits.
- operationId: getRealtimeConfig
- security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - OAuth Authentication
+ summary: Poll device token endpoint
+ description: |
+ RFC8628 token polling endpoint.
+ Returns OAuth errors such as `authorization_pending`, `slow_down`, `access_denied`, and `expired_token`.
+ operationId: authDeviceToken
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - grant_type
+ - device_code
+ - client_id
+ properties:
+ grant_type:
+ type: string
+ enum:
+ - urn:ietf:params:oauth:grant-type:device_code
+ device_code:
+ type: string
+ client_id:
+ type: string
responses:
'200':
- description: Realtime configuration
+ description: Device flow completed, auth-user session minted
content:
application/json:
schema:
- $ref: '#/components/schemas/RealtimeConfig'
- '401':
- description: Unauthorized
+ $ref: '#/components/schemas/AuthTokenResponse'
+ '400':
+ description: Polling state/error response
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '404':
- description: Project not found
+ $ref: '#/components/schemas/OAuthErrorResponse'
+ '403':
+ description: |
+ `access_denied` - the approving account's email domain is not in
+ `allowed_email_domains` while `allowed_email_domains_mode` is
+ `signup_and_signin`. Re-checked here because approval and redemption
+ are separate requests.
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- put:
+ $ref: '#/components/schemas/OAuthErrorResponse'
+ /auth/device/verify:
+ post:
tags:
- - Realtime
- summary: Update realtime configuration for a project
- description: Updates realtime settings including feature toggles and limits.
- operationId: updateRealtimeConfig
+ - OAuth Authentication
+ summary: Approve or deny a device code
+ description: |
+ Browser-side endpoint for authenticated auth-users to approve (`approve`) or deny (`deny`) a `user_code`.
+
+ Called by the verification page after the end user signs in. The grant is
+ scoped to the project the auth-user token belongs to: approving a
+ `user_code` issued for a different project returns `403`. This endpoint
+ does not require managed auth to be enabled, so a custom verification page
+ (hosted anywhere) can drive approval — it just needs an authenticated
+ project auth-user access token and, for cross-origin browser calls, the
+ page origin allowed in the project's auth CORS settings.
+ operationId: authDeviceVerify
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - AuthUserAccessToken: []
requestBody:
required: true
content:
application/json:
schema:
- $ref: '#/components/schemas/UpdateRealtimeConfigRequest'
+ type: object
+ required:
+ - user_code
+ properties:
+ user_code:
+ type: string
+ action:
+ type: string
+ enum:
+ - approve
+ - deny
+ default: approve
responses:
'200':
- description: Updated realtime configuration
+ description: Verification action accepted
content:
application/json:
schema:
- $ref: '#/components/schemas/RealtimeConfig'
- '400':
- description: Invalid configuration values
+ type: object
+ properties:
+ success:
+ type: boolean
+ status:
+ type: string
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '401':
- description: Unauthorized
+ /auth/platform/exchange:
+ post:
+ tags:
+ - OAuth Authentication
+ summary: Exchange auth-user device session for platform token
+ description: |
+ Exchanges a verified auth-user device-flow session into a platform token for CLI usage.
+ The target platform user is derived from authenticated auth-user mapping; client cannot select another user.
+ operationId: authPlatformExchange
+ security:
+ - AuthUserAccessToken: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - client_id
+ properties:
+ client_id:
+ type: string
+ responses:
+ '200':
+ description: Platform token minted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PlatformExchangeResponse'
+ '403':
+ description: |
+ Exchange not allowed for this session/client/project, or the
+ account's email domain is not in `allowed_email_domains` while
+ `allowed_email_domains_mode` is `signup_and_signin`. The domain is
+ re-checked here because the minted platform token outlives the
+ session it is exchanged from.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /projects/{id}/realtime/stats:
+ /auth/oauth/providers:
get:
tags:
- - Realtime
- summary: Get realtime statistics for a project
- description: Returns realtime usage statistics including connection counts and subscribed tables.
- operationId: getRealtimeStats
+ - OAuth Authentication
+ summary: List user's linked providers
+ description: Get list of OAuth providers linked to current user
+ operationId: authListOAuthProviders
security:
- - UserToken: []
- parameters:
- - $ref: '#/components/parameters/ProjectId'
+ - AuthUserAccessToken: []
responses:
'200':
- description: Realtime statistics
+ description: Linked providers
content:
application/json:
schema:
- $ref: '#/components/schemas/RealtimeStats'
+ type: object
+ properties:
+ providers:
+ type: array
+ items:
+ type: object
+ properties:
+ provider:
+ type: string
+ linked_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
'401':
- description: Unauthorized
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /storage/{bucketName}:
- get:
+ /auth/oauth/{provider}/link:
+ post:
tags:
- - Storage Objects
- summary: List objects in a bucket
- operationId: listStorageObjects
+ - OAuth Authentication
+ summary: Link OAuth provider to current user
+ description: |
+ Generates authorization URL to link OAuth provider to existing account.
+ User must be authenticated.
+ operationId: authLinkOAuthProvider
security:
- - AnonKey: []
- - ServiceRoleKey: []
- AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- - name: prefix
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ - name: redirect_url
in: query
- description: Filter objects by path prefix
schema:
type: string
- - name: limit
+ description: |
+ URL to redirect to after linking completes (optional). Same
+ allowed_redirect_urls requirement as GET /auth/oauth/{provider}/authorize.
+ - name: client_state
in: query
- description: Maximum objects to return
schema:
- type: integer
- default: 50
- maximum: 1000
- - name: cursor
+ type: string
+ maxLength: 255
+ description: |
+ Optional application nonce echoed to redirect_url as `state`.
+ - name: response_mode
in: query
- description: Pagination cursor
schema:
type: string
+ enum:
+ - code
+ description: |
+ Set to `code` to receive a short-lived authorization code at
+ redirect_url. `redirect_url` is required in this mode. When
+ omitted, the established session-fragment response is retained for
+ compatibility with existing clients.
responses:
'200':
- description: List of objects
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StorageListResponse'
- '403':
- description: Access denied by storage policy
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- /locks/{key}/lease:
- post:
- tags:
- - Locks
- summary: Acquire a project lock
- description: |
- Acquires a project-scoped lease using the project embedded in the service-role key.
- The caller must hold the `locks.manage` permission. Repeating the request with the
- same lock token is idempotent and resets that lease to the requested TTL. A different
- live owner receives `409 lock_held`; a caller whose own lease already lapsed receives
- `409 lock_ownership_lost`.
- operationId: acquireProjectLock
- security:
- - ServiceRoleKey: []
- parameters:
- - $ref: '#/components/parameters/LockKey'
- - $ref: '#/components/parameters/LockToken'
- - $ref: '#/components/parameters/LockRequestId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectLockLeaseRequest'
- responses:
- '201':
- description: Lease acquired
+ description: Authorization URL generated
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectLockLease'
+ type: object
+ properties:
+ authorization_url:
+ type: string
'400':
- description: Invalid lock key, token, or TTL
+ description: redirect_url is not registered in allowed_redirect_urls
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Missing or invalid credentials
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: A non-service credential was supplied or the service key lacks `locks.manage`
+ '404':
+ description: OAuth provider not configured
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
- description: |
- The lock is held by another live lease (`lock_held`), or the caller's own lease
- lapsed and is not yet reclaimable (`lock_ownership_lost`).
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '429':
- description: Project lock request limit exceeded
- headers:
- Retry-After:
- description: Seconds until the current fixed-minute window ends.
- schema:
- type: integer
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Lock service unavailable
+ description: Provider already linked
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- patch:
+ /auth/oauth/{provider}/unlink:
+ delete:
tags:
- - Locks
- summary: Renew a project lock
+ - OAuth Authentication
+ summary: Unlink OAuth provider
description: |
- Renews a lease owned by the supplied lock token. The request must arrive
- more than one second before `expires_at`; this safety margin prevents
- clock skew between regional API instances from resurrecting an expired
- lease.
- operationId: renewProjectLock
+ Remove OAuth provider from user's account.
+ Cannot unlink if it's the only authentication method.
+ operationId: authUnlinkOAuthProvider
security:
- - ServiceRoleKey: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/LockKey'
- - $ref: '#/components/parameters/LockToken'
- - $ref: '#/components/parameters/LockRequestId'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectLockLeaseRequest'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
responses:
- '200':
- description: Lease renewed
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ProjectLockLease'
+ '204':
+ description: Provider unlinked
'400':
- description: Invalid lock key, token, or TTL
+ description: Cannot unlink last authentication method
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Missing or invalid credentials
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: A non-service credential was supplied or the service key lacks `locks.manage`
+ '404':
+ description: Provider not linked
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: The lease expired or is owned by another token
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '429':
- description: Project lock request limit exceeded
- headers:
- Retry-After:
- description: Seconds until the current fixed-minute window ends.
- schema:
- type: integer
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- '503':
- description: Lock service unavailable
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- delete:
+ /auth/oauth/{provider}/refresh-token:
+ post:
tags:
- - Locks
- summary: Release a project lock
- description: Releases a lease only when the supplied lock token still owns it.
- operationId: releaseProjectLock
+ - OAuth Authentication
+ summary: Refresh OAuth provider token
+ description: |
+ Refresh the access token for an OAuth provider using its refresh token.
+ Allows calling provider APIs on user's behalf (e.g., Google Drive, GitHub repos).
+ operationId: refreshOAuthProviderToken
security:
- - ServiceRoleKey: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/LockKey'
- - $ref: '#/components/parameters/LockToken'
- - $ref: '#/components/parameters/LockRequestId'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
responses:
- '204':
- description: Lease released
+ '200':
+ description: Token refreshed successfully
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ provider:
+ type: string
+ expires_in:
+ type: integer
'400':
- description: Invalid lock key or token
+ description: No refresh token available or refresh failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Missing or invalid credentials
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: A non-service credential was supplied or the service key lacks `locks.manage`
+ '404':
+ description: Provider not linked
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '409':
- description: The lease is owned by another token
+ /auth/oauth/{provider}/token:
+ get:
+ tags:
+ - OAuth Authentication
+ summary: Get current provider access token
+ description: |
+ Get valid access token for OAuth provider.
+ Automatically refreshes if expired.
+ operationId: getOAuthProviderToken
+ security:
+ - AuthUserAccessToken: []
+ parameters:
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ responses:
+ '200':
+ description: Current access token
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '429':
- description: Project lock request limit exceeded
- headers:
- Retry-After:
- description: Seconds until the current fixed-minute window ends.
- schema:
- type: integer
+ type: object
+ properties:
+ message:
+ type: string
+ provider:
+ type: string
+ expires_in:
+ type: integer
+ '401':
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Lock service unavailable
+ '404':
+ description: Provider not linked
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /locks/{key}:
- get:
+ /auth/oauth/{provider}/call-api:
+ post:
tags:
- - Locks
- summary: Read a project lock
+ - OAuth Authentication
+ summary: Call OAuth provider API
description: |
- Reports whether the lock is currently held, when its lease expires, and the
- holder's fencing token. `held` follows takeover eligibility rather than raw
- expiry, so `held: false` means an acquire would succeed now. No lock token is
- required, making this usable for monitoring and recovery.
- operationId: getProjectLock
+ Make an authenticated request to an OAuth provider's API on behalf of the user.
+ The user's stored access token is automatically used and refreshed if needed.
+
+ The request is always sent to the provider's fixed API base URL joined with
+ the caller-supplied `endpoint`. `endpoint` must be a relative path beginning
+ with `/` (optionally with a query string); it cannot change the target host.
+ Absolute URLs, protocol-relative `//host` values, or userinfo (`@host`) are
+ rejected with `400` so the request can never be redirected to another host.
+
+ Examples of `endpoint`:
+ - Google userinfo: `/oauth2/v1/userinfo`
+ - GitHub repositories: `/user/repos`
+ - Microsoft Graph profile: `/me`
+
+ The response wraps the provider's raw JSON value with request metadata.
+ An empty provider body is represented as `data: null`; the envelope
+ preserves the provider's HTTP status in `status_code`, including errors.
+ Provider response bodies are limited to 8 MiB after decompression.
+ Transport failures, invalid JSON (including invalid UTF-8), and oversized
+ bodies return `502`. Provider redirects to another origin are blocked and
+ return `400`.
+ operationId: callOAuthProviderAPI
security:
- - ServiceRoleKey: []
+ - AuthUserAccessToken: []
parameters:
- - $ref: '#/components/parameters/LockKey'
- - $ref: '#/components/parameters/LockRequestId'
+ - name: provider
+ in: path
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - endpoint
+ properties:
+ endpoint:
+ type: string
+ description: |
+ Relative path on the provider's API, beginning with `/`. It is
+ joined with the provider's fixed base URL; it must not contain a
+ scheme, host, userinfo, or a leading `//`.
+ example: /user/repos
+ method:
+ type: string
+ enum:
+ - GET
+ - POST
+ default: GET
+ description: HTTP method to use
+ body:
+ type: object
+ additionalProperties: true
+ description: Request body for POST requests
responses:
'200':
- description: Current lock state
+ description: Provider API response
content:
application/json:
schema:
- $ref: '#/components/schemas/ProjectLockState'
+ type: object
+ description: OAuth provider API response envelope
+ required:
+ - provider
+ - endpoint
+ - status_code
+ - data
+ properties:
+ provider:
+ type: string
+ enum:
+ - google
+ - github
+ - microsoft
+ - apple
+ endpoint:
+ type: string
+ status_code:
+ type: integer
+ minimum: 100
+ maximum: 599
+ data:
+ description: Raw provider JSON value, or null when the provider returns no body
+ nullable: true
'400':
- description: Invalid lock key
+ description: |
+ Invalid request (for example: missing `endpoint`, an `endpoint` that is
+ not a relative path, or an unsupported HTTP method), or a provider
+ redirect to another origin.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
- description: Missing or invalid credentials
+ description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '403':
- description: A non-service credential was supplied or the service key lacks `locks.manage`
+ '404':
+ description: OAuth provider configuration not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '429':
- description: Project lock request limit exceeded
- headers:
- Retry-After:
- description: Seconds until the current fixed-minute window ends.
- schema:
- type: integer
+ '500':
+ description: Failed to create the provider API request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Lock service unavailable
+ '502':
+ description: Provider transport failure, invalid JSON, or response body larger than 8 MiB
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- delete:
+ /projects/{id}/anon-keys:
+ get:
tags:
- - Locks
- summary: Force release a project lock
- description: |
- Drops the lease whatever token holds it, for recovering a lock whose holder
- died without releasing. Use `DELETE /locks/{key}/lease` for normal release.
-
- This breaks mutual exclusion by itself: the previous holder keeps working
- until its own renewal fails. Guard the protected resource with the lease's
- `fencing_token`, which the next acquisition raises, so a write from the
- displaced holder can be rejected. Succeeds when the lock is already absent.
- operationId: forceReleaseProjectLock
+ - Anon Keys
+ summary: List anon keys
+ description: |
+ Supports two mutually exclusive pagination modes. Offset mode uses `page`
+ and `limit` and is the default when neither `cursor` nor `search` is
+ supplied (first page, default `limit`). Cursor mode uses `cursor` and
+ `limit`, supports `search` (case-insensitive name match), and returns
+ `next_cursor`. Sending both `page` and `cursor` (or `page` and `search`)
+ returns 400.
+ operationId: listAnonKeys
security:
- - ServiceRoleKey: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - $ref: '#/components/parameters/LockKey'
- - $ref: '#/components/parameters/LockRequestId'
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
responses:
- '204':
- description: Lock released
- '400':
- description: Invalid lock key
+ '200':
+ description: List of anon keys
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '401':
- description: Missing or invalid credentials
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/AnonKey'
+ total:
+ type: integer
+ description: Total number of items matching the query (so the UI can render numbered pages).
+ has_more:
+ type: boolean
+ description: Whether a next page exists.
+ next_cursor:
+ type: string
+ description: Opaque cursor for the next page (cursor pagination only)
+ prev_cursor:
+ type: string
+ description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
+ post:
+ tags:
+ - Anon Keys
+ summary: Create anon key
+ operationId: createAnonKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ description: |
+ Key name for identification.
+ Can only contain letters, numbers, underscores, and hyphens.
+ pattern: ^[A-Za-z0-9_-]+$
+ minLength: 1
+ maxLength: 255
+ example: frontend-app
+ permissions:
+ type: array
+ items:
+ type: string
+ enum:
+ - auth.signup
+ - auth.signin
+ - auth.refresh
+ - auth.logout
+ - auth.password_reset
+ - auth.confirm_email
+ - auth.resend_confirmation
+ - storage.upload
+ - storage.download
+ - storage.list
+ - storage.delete
+ - realtime.connect
+ - realtime.subscribe
+ - realtime.publish
+ - functions.invoke
+ description: |
+ Optional list of permissions for this key.
+ If not provided, defaults to auth-only permissions: auth.signup, auth.signin, auth.refresh, auth.logout, auth.password_reset, auth.confirm_email, auth.resend_confirmation.
+ Storage, realtime, and functions permissions must be explicitly added if needed.
+ example:
+ - auth.signup
+ - auth.signin
+ - auth.refresh
+ - auth.logout
+ responses:
+ '201':
+ description: Anon key created
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '403':
- description: A non-service credential was supplied or the service key lacks `locks.manage`
+ $ref: '#/components/schemas/AnonKey'
+ /projects/{id}/anon-keys/{keyId}:
+ get:
+ tags:
+ - Anon Keys
+ summary: Get anon key
+ description: Get details of a specific anon key
+ operationId: getAnonKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: Anon key details
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
- '429':
- description: Project lock request limit exceeded
- headers:
- Retry-After:
- description: Seconds until the current fixed-minute window ends.
- schema:
- type: integer
+ $ref: '#/components/schemas/AnonKey'
+ '404':
+ description: Key not found
+ delete:
+ tags:
+ - Anon Keys
+ summary: Revoke anon key
+ description: Revokes key - it will immediately stop working
+ operationId: revokeAnonKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '204':
+ description: Key revoked
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Key not found
+ '409':
+ description: Cannot delete the project's default anon key
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- '503':
- description: Lock service unavailable
+ '500':
+ description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
- /storage/{bucketName}/move:
+ /projects/{id}/anon-keys/{keyId}/regenerate:
post:
tags:
- - Storage Objects
- summary: Move/rename an object
- operationId: moveStorageObject
+ - Anon Keys
+ summary: Regenerate anon key
+ description: Generate new JWT value for existing key
+ operationId: regenerateAnonKey
security:
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StorageMoveRequest'
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
responses:
'200':
- description: Object moved
+ description: Key regenerated
content:
application/json:
schema:
- $ref: '#/components/schemas/StorageObject'
- '403':
- description: Access denied by storage policy
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- /storage/{bucketName}/copy:
+ $ref: '#/components/schemas/AnonKey'
+ /projects/{id}/anon-keys/{keyId}/set-default:
post:
tags:
- - Storage Objects
- summary: Copy an object
- operationId: copyStorageObject
+ - Anon Keys
+ summary: Set default anon key
+ description: Promotes the given key to the project's configured default. At most one key per project can be default.
+ operationId: setDefaultAnonKey
security:
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StorageCopyRequest'
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
responses:
- '201':
- description: Object copied
+ '200':
+ description: Key set as default
content:
application/json:
schema:
- $ref: '#/components/schemas/StorageObject'
+ $ref: '#/components/schemas/AnonKey'
+ '401':
+ description: Unauthorized
'403':
- description: Access denied by storage policy
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- /storage/{bucketName}/{path}:
- post:
- tags:
- - Storage Objects
- summary: Upload a file or create resumable session
+ description: Forbidden
+ '404':
+ description: Anon key not found
+ '500':
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/access-tokens:
+ get:
+ tags:
+ - Project Access Tokens
+ summary: List a project's access tokens
description: |
- Unified endpoint for file uploads. Behavior depends on Content-Type and headers:
-
- **Simple Upload (multipart/form-data):**
- Upload a complete file in a single request. Best for files under 100MB.
+ Lists the project's access tokens, newest first. Secrets are never
+ returned: only a hash is stored, so a token's value exists solely in the
+ response to the create call.
- **Create Resumable Session (application/json):**
- Create a session for chunked uploads. Best for large files or unreliable networks.
- Requires: `Content-Type: application/json` with body `{"filename": "...", "content_type": "...", "total_size": ...}`
-
- **Complete Resumable Session:**
- Complete a session after all parts are uploaded.
- Requires: `X-Upload-Session` header with session ID and `X-Upload-Complete: true` header.
+ Only tokens that can still authenticate are returned by default, so
+ revoked and expired ones are hidden. Pass `include_revoked=true` to see
+ them, which is how you find out what a key did before it stopped working.
- **Resumable Session Ownership:**
- A session created with a user access token remains bound to that user. A session
- created with an anon key remains bound to that exact anon key. Reuse the same
- identity or anon key for part uploads, status, completion, and abort requests;
- an ownership mismatch returns `404`.
- operationId: uploadStorageObject
+ Requires a platform token. A project access token cannot manage project
+ access tokens, so a leaked credential cannot enumerate or replace itself.
+ operationId: listProjectAccessTokens
security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- - name: path
- in: path
- required: true
- description: Object path within bucket
- schema:
- type: string
- - name: X-Upload-Session
- in: header
- required: false
- description: Upload session ID (for completing resumable uploads)
- schema:
- type: string
- - name: X-Upload-Complete
- in: header
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Search'
+ - name: include_revoked
+ in: query
required: false
- description: Set to "true" to complete a resumable upload session
+ description: |
+ Include tokens that can no longer authenticate — both revoked and
+ expired ones.
schema:
- type: string
- enum:
- - 'true'
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- required:
- - file
- properties:
- file:
- type: string
- format: binary
- description: File to upload (simple upload)
- application/json:
- schema:
- $ref: '#/components/schemas/CreateUploadSessionRequest'
+ type: boolean
+ default: false
responses:
'200':
- description: Resumable upload completed (when X-Upload-Complete=true)
+ description: Successful response
content:
application/json:
schema:
- $ref: '#/components/schemas/CompleteUploadSessionResponse'
- '201':
- description: File uploaded or session created
+ $ref: '#/components/schemas/PaginatedProjectAccessTokens'
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- oneOf:
- - $ref: '#/components/schemas/StorageObject'
- - $ref: '#/components/schemas/CreateUploadSessionResponse'
- '400':
- description: |
- Bad request. This can occur when:
- - MIME type is not in the bucket's allowed_mime_types list
- - File exceeds the bucket's configured file_size_limit
- - File exceeds the global maximum upload size (5GB)
- - Invalid request body or missing required fields
+ $ref: '#/components/schemas/Error'
'403':
- description: Access denied by storage policy
+ description: Forbidden - not the project owner, or a project access token was used
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Resumable upload session not found or not owned by this credential
- '413':
- description: |
- File size exceeds plan-based limits. This occurs when:
- - File exceeds the plan-based maximum file size (FREE or PRO tier)
- - Upload would exceed the project's total storage quota
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- put:
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ post:
tags:
- - Storage Objects
- summary: Upload a part of a resumable upload
+ - Project Access Tokens
+ summary: Create a project access token
description: |
- Upload a single part of a resumable upload session.
+ Creates a project access token and returns its secret.
- **Requirements:**
- - Part numbers start at 1
- - All parts except the last must be at least 5MB
- - Maximum part size is 25MB
- - Parts can be uploaded in any order
- - Re-uploading a part overwrites the previous upload
- - Anonymous sessions must reuse the exact anon key that created the session
- operationId: uploadPart
+ The secret is in this response and nowhere else. Only its hash is
+ stored, so it cannot be retrieved, displayed, or recovered later — save
+ it when you create it.
+
+ The name must be unique within the project, so a retry cannot mint a
+ second credential. It cannot recover the first one either. A retry that
+ returns `409` with code `access_token_name_exists` means the original
+ create committed and its secret is unrecoverable: list the project's
+ tokens, revoke the one holding that name, and create it again.
+
+ Requires a platform token.
+ operationId: createProjectAccessToken
security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- - name: path
- in: path
- required: true
- description: Object path within bucket
- schema:
- type: string
- - name: X-Upload-Session
- in: header
- required: true
- description: Upload session ID
- schema:
- type: string
- - name: X-Part-Number
- in: header
- required: true
- description: Part number (1 to 10000)
- schema:
- type: integer
- minimum: 1
- maximum: 10000
+ - $ref: '#/components/parameters/ProjectId'
requestBody:
required: true
content:
- application/octet-stream:
+ application/json:
schema:
- type: string
- format: binary
+ $ref: '#/components/schemas/CreateProjectAccessTokenRequest'
responses:
- '200':
- description: Part uploaded
+ '201':
+ description: Token created - save the secret now, it cannot be retrieved again
content:
application/json:
schema:
- $ref: '#/components/schemas/UploadSessionPart'
+ $ref: '#/components/schemas/CreatedProjectAccessToken'
'400':
- description: Invalid part number or part data
+ description: Bad request - invalid name, scope, or expiry
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: Access denied
+ description: Forbidden - not the project owner, or a project access token was used
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Session not found or not owned by this credential
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: |
+ Duplicate name, token limit reached, or the project is being
+ deleted. Tell them apart with `code`, which is one of
+ `access_token_name_exists`, `access_token_limit_reached`, or
+ `project_deleting` — the message text is not a contract.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/access-tokens/usage:
get:
tags:
- - Storage Objects
- summary: Download a file or get upload session status
+ - Project Access Tokens
+ summary: Per-day request counts for every access token in a project
description: |
- Download a file, or get the status of a resumable upload session.
+ Returns a zero-filled daily series of request counts for each of the
+ project's access tokens, oldest first. Every day in the window is
+ present, so a gap reads as zero rather than missing.
- **File Download (default):**
- Downloads the file at the specified path.
+ Revoked tokens are included, because the traffic they made before
+ revocation is usually the reason you are looking.
- **Session Status (with X-Upload-Session header):**
- Returns the status of a resumable upload session, including which parts have been uploaded.
- Anonymous sessions must reuse the exact anon key that created the session.
- operationId: downloadStorageObject
+ `days` defaults to 30 and is capped at 60, which is also how long per-day
+ counts are retained — a longer window cannot be answered.
+
+ A platform token sees every token in the project. A project access token
+ sees only its own row, so it can watch its own traffic without being
+ able to enumerate the project's other credentials by name.
+ operationId: listProjectAccessTokensUsage
security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- - name: path
- in: path
- required: true
- description: Object path within bucket
- schema:
- type: string
- - name: Range
- in: header
- required: false
- description: |
- HTTP Range header for partial downloads.
- Format: bytes=start-end or bytes=start-
- Examples: bytes=0-1023, bytes=1000-
- schema:
- type: string
- pattern: ^bytes=\d+-\d*$
- - name: X-Upload-Session
- in: header
+ - $ref: '#/components/parameters/ProjectId'
+ - name: days
+ in: query
required: false
- description: Upload session ID (to get session status instead of downloading)
+ description: Number of trailing days to return (1-60, default 30).
schema:
- type: string
+ type: integer
+ minimum: 1
+ maximum: 60
+ default: 30
responses:
'200':
- description: File content or session status
- headers:
- Content-Type:
- schema:
- type: string
- Content-Length:
+ description: Successful response
+ content:
+ application/json:
schema:
- type: integer
- ETag:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProjectAccessTokenUsage'
+ '400':
+ description: Bad request - invalid window
+ content:
+ application/json:
schema:
- type: string
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
content:
- application/octet-stream:
+ application/json:
schema:
- type: string
- format: binary
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - not the project owner
+ content:
application/json:
schema:
- $ref: '#/components/schemas/UploadSessionStatusResponse'
- '206':
- description: Partial content (range request)
- '400':
- description: Invalid Range header format
- '403':
- description: Access denied by storage policy
+ $ref: '#/components/schemas/Error'
'404':
- description: Object or session not found, or session not owned by this credential
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- delete:
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/access-tokens/{tokenId}:
+ get:
tags:
- - Storage Objects
- summary: Delete a file or abort upload session
+ - Project Access Tokens
+ summary: Get a project access token
description: |
- Delete a file, or abort a resumable upload session.
-
- **File Delete (default):**
- Deletes the file at the specified path.
+ Returns one token's metadata. Never its secret, which is not stored in a
+ recoverable form.
- **Abort Session (with X-Upload-Session header):**
- Aborts a resumable upload session and cleans up any uploaded parts.
- Anonymous sessions must reuse the exact anon key that created the session.
- operationId: deleteStorageObject
+ Requires a platform token.
+ operationId: getProjectAccessToken
security:
- - AnonKey: []
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- - name: path
- in: path
- required: true
- description: Object path within bucket
- schema:
- type: string
- - name: X-Upload-Session
- in: header
- required: false
- description: Upload session ID (to abort session instead of deleting file)
- schema:
- type: string
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/TokenId'
responses:
'200':
- description: Object deleted or session aborted
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectAccessToken'
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'403':
- description: Access denied by storage policy
+ description: Forbidden - not the project owner, or a project access token was used
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Object or session not found, or session not owned by this credential
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- /storage/{bucketName}/{path}/visibility:
- patch:
+ description: Token not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ delete:
tags:
- - Storage Objects
- summary: Update file visibility (public/private)
+ - Project Access Tokens
+ summary: Revoke a project access token
description: |
- Change whether a file is publicly accessible. Only the file owner or a service key can change visibility.
- If the bucket defines UPDATE policies, the owner must also satisfy one of them.
+ Revokes the token. It stops authenticating immediately in the region
+ handling this call and within seconds across Volcano's other regions.
- - Public files can be downloaded with just an anon key (no user authentication required)
- - Private files (default) require authentication and must pass policy checks
- - All downloads go through the Volcano API - there is no direct access to the underlying store
- operationId: updateStorageObjectVisibility
+ The record is kept rather than deleted, so the token's name, prefix, last
+ use, and request history stay available — which is what you need if you
+ are revoking because a secret leaked. Revoking an already-revoked token
+ succeeds.
+
+ Revoking does not undo anything the token already did. Treat whatever it
+ could reach as exposed and rotate accordingly.
+
+ Requires a platform token.
+ operationId: revokeProjectAccessToken
security:
- - ServiceRoleKey: []
- - AuthUserAccessToken: []
+ - UserToken: []
parameters:
- - $ref: '#/components/parameters/BucketName'
- - name: path
- in: path
- required: true
- description: Object path within bucket
- schema:
- type: string
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/StorageVisibilityRequest'
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/TokenId'
responses:
- '200':
- description: Visibility updated
+ '204':
+ description: Token revoked
+ '401':
+ description: Unauthorized - invalid or missing token
content:
application/json:
schema:
- $ref: '#/components/schemas/StorageObject'
+ $ref: '#/components/schemas/Error'
'403':
- description: Not the file owner or denied by the bucket's UPDATE policies
+ description: Forbidden - not the project owner, or a project access token was used
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Object not found
- /public/{projectId}/{bucketName}/{path}:
+ description: Token not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: Conflict - the project is being deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/access-tokens/{tokenId}/usage:
get:
tags:
- - Storage Objects
- summary: Download a public file (no authentication required)
+ - Project Access Tokens
+ summary: Per-day request counts for one access token
description: |
- Download a file that has been marked as public. This endpoint requires NO authentication.
-
- **Access Requirements:**
- - The file must have `is_public: true` set via the visibility endpoint
- - Private files will return 403 Forbidden
-
- **Use Cases:**
- - Shareable public URLs for profile pictures, public documents, etc.
- - Embedding public files on external websites
- - Direct linking without requiring SDK or authentication
-
- **URL Format:**
- ```
- GET /public/{projectId}/{bucketName}/{path}
- ```
+ Returns a zero-filled daily series of request counts for a single token,
+ oldest first, so the response always has exactly `days` entries.
- **Example:**
- ```
- https://api.volcano.dev/public/abc123/avatars/user-photo.jpg
- ```
+ `days` defaults to 30 and is capped at 60, matching how long per-day
+ counts are retained.
- **CORS:**
- This endpoint allows all origins since the file is already public.
- operationId: downloadPublicFile
+ A project access token may read only its own usage; asking for another
+ token's returns `403`. A platform token may read any token in the
+ project.
+ operationId: getProjectAccessTokenUsage
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
parameters:
- - name: projectId
- in: path
- required: true
- description: Project ID
- schema:
- type: string
- format: uuid
- - $ref: '#/components/parameters/BucketName'
- - name: path
- in: path
- required: true
- description: Object path within bucket
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/TokenId'
+ - name: days
+ in: query
+ required: false
+ description: Number of trailing days to return (1-60, default 30).
schema:
- type: string
+ type: integer
+ minimum: 1
+ maximum: 60
+ default: 30
responses:
'200':
- description: File content
+ description: Successful response
content:
- '*/*':
+ application/json:
schema:
- type: string
- format: binary
- '206':
- description: Partial content (range request)
+ $ref: '#/components/schemas/ProjectAccessTokenUsage'
+ '400':
+ description: Bad request - invalid window
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized - invalid or missing token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Forbidden - not the project owner
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'404':
- description: Not found (file doesn't exist or is not public)
- '429':
- $ref: '#/components/responses/BandwidthCapExceeded'
- /health:
+ description: Token not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/service-keys:
get:
tags:
- - System
- summary: Health check endpoint
- description: Returns server health status. Used for load balancer and monitoring checks.
- operationId: healthCheck
+ - Service Keys
+ summary: List service keys (paginated)
+ description: |
+ List all service role keys for a project with pagination.
+
+ **WARNING:** Service keys bypass RLS - for backend/admin use only!
+ operationId: listServiceKeys
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
responses:
'200':
- description: Server is healthy
+ description: Paginated list of service keys
content:
- text/plain:
+ application/json:
schema:
- type: string
- example: OK
-components:
- securitySchemes:
- AnonKey:
- type: http
- scheme: bearer
- bearerFormat: JWT
- description: |
- Project-specific public key for frontend authentication.
- Required for signup, signin, refresh, and logout endpoints.
- Get from Project Settings → Authentication → Anon Keys.
- Safe to expose in frontend code (scoped to project, limited permissions).
- AuthUserAccessToken:
- type: http
- scheme: bearer
- bearerFormat: JWT
+ $ref: '#/components/schemas/PaginatedServiceKeys'
+ post:
+ tags:
+ - Service Keys
+ summary: Create service key
description: |
- Auth user access token obtained from signup/signin.
- Used for authenticated function invocation and user profile access.
- Functions invoked with access tokens receive user context in event.__volcano_auth.
- Expires after configured lifetime (default: 1 hour).
- ServiceRoleKey:
- type: http
- scheme: bearer
- bearerFormat: JWT
- description: |
- Service role key for admin operations.
- **WARNING:** Bypasses Row-Level Security - backend use only!
- Create via POST /projects/{id}/service-keys.
- Used for function invocation with full database access.
- UserToken:
- type: http
- scheme: bearer
- bearerFormat: JWT
- description: |
- Platform user token from the Management API.
- Required for project management operations.
- Obtain via POST /tokens in Management API (port 8001).
- parameters:
- BackupName:
- name: backupName
- in: path
- required: true
- schema:
- type: string
- minLength: 1
- maxLength: 128
- description: |
- Backup name, unique within the database, exactly as returned by the list
- endpoint.
+ Create a new service role key for admin operations.
- Deliberately looser than the names you can create: a backup made by a
- schedule is named for you, so reading or deleting one accepts any name a
- backup can have.
- BranchName:
- name: branchName
- in: path
- required: true
- schema:
- type: string
- pattern: ^[a-z0-9_]+$
- maxLength: 64
- description: Branch name (unique within the parent database, lowercase letters, numbers, and underscores only)
- BucketName:
- name: bucketName
- in: path
- required: true
- schema:
- type: string
- pattern: ^[a-zA-Z0-9_-]+$
- minLength: 1
- maxLength: 64
- description: Storage bucket name
- Cursor:
- name: cursor
- in: query
- required: false
- schema:
- type: string
- description: |
- Opaque keyset pagination cursor from a previous response's `next_cursor`
- — pages forward. Mutually exclusive with `page` and `ending_before`;
- combining them returns 400. When supplied, the request's `search` and
- `limit` must match the values bound to the cursor or the request returns 400.
- EndingBefore:
- name: ending_before
- in: query
- required: false
- schema:
- type: string
+ **WARNING:** Service keys bypass all RLS policies!
+ Store securely and NEVER expose in frontend code.
+ operationId: createServiceKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ description: |
+ Descriptive name for the key (e.g., "admin-dashboard", "background-jobs").
+ Can only contain letters, numbers, underscores, and hyphens.
+ pattern: ^[A-Za-z0-9_-]+$
+ minLength: 1
+ maxLength: 255
+ example: admin-dashboard
+ permissions:
+ type: array
+ items:
+ type: string
+ description: |
+ Optional least-privilege scope for the key. When omitted, empty, or
+ containing only blank strings, the key is granted full access (["*"])
+ for backward compatibility. Provide an explicit list (e.g.
+ ["functions.invoke", "locks.manage"]) to restrict the key; "*"
+ grants everything. Scope enforcement applies to function invocation,
+ storage object operations, and project locks.
+ example:
+ - functions.invoke
+ - locks.manage
+ responses:
+ '201':
+ description: Service key created - save the key_value immediately!
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ServiceKey'
+ '409':
+ description: Key with this name already exists
+ /projects/{id}/service-keys/{keyId}:
+ get:
+ tags:
+ - Service Keys
+ summary: Get service key
+ description: Get details of a specific service key
+ operationId: getServiceKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: Service key details
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ServiceKey'
+ '404':
+ description: Key not found
+ delete:
+ tags:
+ - Service Keys
+ summary: Delete service key
description: |
- Opaque keyset pagination cursor from a previous response's `prev_cursor`
- — pages backward (the page immediately preceding this cursor). Mutually
- exclusive with `page` and `cursor`; combining them returns 400. `search`
- and `limit` must match the values bound to the cursor or the request
- returns 400.
- DatabaseName:
- name: databaseName
- in: path
- required: true
- schema:
- type: string
- pattern: ^[a-z0-9_]+$
- maxLength: 64
- description: Database name (unique within project, lowercase letters, numbers, and underscores only)
- DeploymentId:
- name: deploymentId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Frontend deployment ID
- FrontendId:
- name: frontendId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Frontend ID
- FunctionId:
- name: functionId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Function ID
- DurableFunctionId:
- name: functionId
- in: path
- required: true
- schema:
- type: string
- description: Durable function ID, or its name within the project
- DurableExecutionId:
- name: executionId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Durable execution ID
- Limit:
- name: limit
- in: query
- required: false
- schema:
- type: integer
- minimum: 1
- maximum: 100
- default: 10
- description: Number of items per page (max 100)
- LockKey:
- name: key
- in: path
- required: true
- description: Project-local lock name.
- schema:
- type: string
- minLength: 1
- maxLength: 128
- pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
- LockToken:
- name: X-Volcano-Lock-Token
- in: header
- required: true
- description: Opaque UUID generated once by the caller and retained for the lease lifetime.
- schema:
- type: string
- format: uuid
- LockRequestId:
- name: X-Volcano-Request-Id
- in: header
- required: true
+ Permanently delete a service key.
+ Any services using this key will immediately lose access.
+ operationId: deleteServiceKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '204':
+ description: Key deleted
+ /projects/{id}/service-keys/{keyId}/regenerate:
+ post:
+ tags:
+ - Service Keys
+ summary: Regenerate service key
description: |
- UUID correlating this request across client and server logs. Repeat safety comes from
- the lock token, so a retry under a reused request ID still counts against the quota.
- schema:
- type: string
- format: uuid
- DeploymentOperation:
- name: operation
- in: query
- required: false
- description: Restrict a deployment feed to one kind of operation.
- schema:
- type: string
- enum:
- - deploy
- - redeploy
- - update
- - delete
- DeploymentOwnerId:
- name: owner_id
- in: query
- required: false
- description: |
- The user who owns the projects whose deployments to return
- (`projects.user_id`). This is ownership, not the actor that started the
- deployment — see `initiated_by_user_id` for that. Not a UUID: platform
- user ids are opaque strings.
- schema:
- type: string
- maxLength: 255
- DeploymentOrder:
- name: order
- in: query
- required: false
- description: |
- Sort key and direction. `created_at.desc` (default) is the feed order.
- `completed_at.asc` orders finished attempts by completion, oldest first,
- and excludes attempts that never completed.
- schema:
- type: string
- enum:
- - created_at.desc
- - completed_at.asc
- default: created_at.desc
- DeploymentResourceType:
- name: resource_type
- in: query
- required: false
- description: |
- Restrict a deployment feed to a single resource type. Omit to return
- both Function and Frontend deployments.
- schema:
- type: string
- enum:
- - function
- - frontend
- DeploymentStatus:
- name: status
- in: query
- required: false
- description: Restrict a deployment feed to attempts in one status.
- schema:
- type: string
- enum:
- - queued
- - provisioning
- - active
- - degraded
- - failed
- - superseded
- - deleting
- - deleted
- Offset:
- name: offset
- in: query
- required: false
- schema:
- type: integer
- minimum: 0
- default: 0
- description: |
- Bounded row offset past the keyset anchor named by `cursor` (forward) or
- `ending_before` (backward) — the hybrid jump. Seek to the anchor, then
- skip this many rows within. Used for numbered jump-to-page: from the
- current page, seek to its next/prev cursor and offset the remaining
- pages. Only honored on the cursor pagination path; ignored otherwise.
- Page:
- name: page
- in: query
- required: false
- schema:
- type: integer
- minimum: 1
- description: |
- Page number (1-indexed) for offset pagination. Declares no schema
- default so the request validator does not inject one: handlers that omit
- `page` see it unset (nil) and default to 1 in code, while cursor-first
- endpoints (e.g. the project deployments feed) can detect its absence to
- stay in keyset/search mode. Supplying `page` selects offset pagination.
- ProjectId:
- name: id
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Project ID
- Search:
- name: search
- in: query
- required: false
- schema:
- type: string
- maxLength: 256
- description: |
- Case-insensitive substring match on the resource `name`. See the
- endpoint description for supported pagination modes.
- RestoreId:
- name: restoreId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Database restore ID
- SchedulerId:
- name: schedulerId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- description: Function scheduler ID
- VariableName:
- name: name
- in: path
- required: true
- schema:
- type: string
- minLength: 1
- maxLength: 256
- pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
- description: Variable name
- responses:
- BandwidthCapExceeded:
- description: |
- The platform user exceeded their billing-cycle bandwidth allowance (aggregate
- ingress + egress across owned projects). Enforcement is eventual:
- requests are rejected until the allowance increases or the next
- anniversary cycle begins.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- DatabaseQueryCapExceeded:
- description: |
- The query was rejected by a billing-cycle allowance: either the owning
- platform user's bandwidth allowance (aggregate ingress + egress across
- owned projects) or their database-request allowance. Enforcement is
- eventual: queries are rejected until the allowance increases or the
- next anniversary cycle begins. The error message identifies the resource.
- content:
- application/json:
+ Generate new JWT value for existing key.
+ The old key stops working within a few seconds.
+ Update your backend services with the new key before regenerating in production.
+ operationId: regenerateServiceKey
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: keyId
+ in: path
+ required: true
schema:
- $ref: '#/components/schemas/Error'
- DatabaseBranchQueryUnavailable:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: Key regenerated - save the new key_value immediately!
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ServiceKey'
+ /projects/{id}/storage/buckets:
+ get:
+ tags:
+ - Storage Buckets
+ summary: List all storage buckets in a project
description: |
- The branch exists but cannot serve queries: it is still provisioning,
- being reset, expired, or its parent is being restored. Distinct from
- `404` so a caller waiting on a branch can tell it apart from a typo.
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
- schemas:
+ With no pagination params, returns the full bucket list as a bare array
+ (legacy). Supplying `cursor`, `ending_before`, `search`, or `limit`
+ switches to keyset (cursor) pagination and returns a paginated envelope
+ with `next_cursor`/`prev_cursor` and a filtered `total`.
+ operationId: listStorageBuckets
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/Limit'
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: |
+ Either the full bucket list (bare array, legacy) or a paginated
+ envelope when cursor pagination is requested.
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: array
+ items:
+ $ref: '#/components/schemas/StorageBucket'
+ - $ref: '#/components/schemas/PaginatedStorageBuckets'
+ post:
+ tags:
+ - Storage Buckets
+ summary: Create a new storage bucket
+ operationId: createStorageBucket
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateStorageBucketRequest'
+ responses:
+ '201':
+ description: Bucket created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageBucket'
+ '409':
+ description: Bucket already exists
+ /projects/{id}/storage/buckets/{bucketName}:
+ get:
+ tags:
+ - Storage Buckets
+ summary: Get storage bucket by name
+ operationId: getStorageBucket
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/BucketName'
+ responses:
+ '200':
+ description: Bucket details
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageBucket'
+ '404':
+ description: Bucket not found
+ patch:
+ tags:
+ - Storage Buckets
+ summary: Update storage bucket settings
+ operationId: updateStorageBucket
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/BucketName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateStorageBucketRequest'
+ responses:
+ '200':
+ description: Bucket updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageBucket'
+ delete:
+ tags:
+ - Storage Buckets
+ summary: Delete storage bucket and all objects
+ operationId: deleteStorageBucket
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/BucketName'
+ responses:
+ '200':
+ description: Bucket deleted
+ /projects/{id}/storage/buckets/{bucketName}/policies:
+ get:
+ tags:
+ - Storage Policies
+ summary: List storage policies for a bucket
+ operationId: listStoragePolicies
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/BucketName'
+ responses:
+ '200':
+ description: List of policies
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/StoragePolicy'
+ post:
+ tags:
+ - Storage Policies
+ summary: Create a storage policy
+ operationId: createStoragePolicy
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/BucketName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateStoragePolicyRequest'
+ responses:
+ '201':
+ description: Policy created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StoragePolicy'
+ /projects/{id}/storage/buckets/{bucketName}/policies/{policyId}:
+ delete:
+ tags:
+ - Storage Policies
+ summary: Delete a storage policy
+ operationId: deleteStoragePolicy
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - $ref: '#/components/parameters/BucketName'
+ - name: policyId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: Policy deleted
+ /projects/{id}/storage/objects:
+ get:
+ tags:
+ - Storage Admin
+ summary: List all storage objects in a project
+ description: |
+ Returns a paginated list of all storage objects across all buckets in the project.
+ Supports filtering by owner and pagination.
+ operationId: listStorageObjectsAdmin
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ - name: owner_id
+ in: query
+ description: Filter by owner user ID
+ schema:
+ type: string
+ format: uuid
+ - name: page
+ in: query
+ description: Page number (1-based)
+ schema:
+ type: integer
+ default: 1
+ minimum: 1
+ - name: limit
+ in: query
+ description: Items per page
+ schema:
+ type: integer
+ default: 50
+ minimum: 1
+ maximum: 100
+ - $ref: '#/components/parameters/Cursor'
+ - $ref: '#/components/parameters/EndingBefore'
+ - $ref: '#/components/parameters/Offset'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: Paginated list of storage objects
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/StorageObjectWithBucket'
+ page:
+ type: integer
+ limit:
+ type: integer
+ total:
+ type: integer
+ has_more:
+ type: boolean
+ next_cursor:
+ type: string
+ description: Opaque cursor for the next page (cursor pagination only)
+ prev_cursor:
+ type: string
+ description: Opaque cursor for the previous page (cursor pagination only). Send as `ending_before`.
+ /projects/{id}/storage/stats:
+ get:
+ tags:
+ - Storage Admin
+ summary: Get storage statistics for a project
+ description: Returns aggregate storage statistics including bucket count, object count, and total size.
+ operationId: getStorageStats
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '200':
+ description: Storage statistics
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageStats'
+ /projects/{id}/realtime/config:
+ get:
+ tags:
+ - Realtime
+ summary: Get realtime configuration for a project
+ description: Returns the realtime configuration including enabled features and limits.
+ operationId: getRealtimeConfig
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '200':
+ description: Realtime configuration
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RealtimeConfig'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: Project not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ put:
+ tags:
+ - Realtime
+ summary: Update realtime configuration for a project
+ description: Updates realtime settings including feature toggles and limits.
+ operationId: updateRealtimeConfig
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateRealtimeConfigRequest'
+ responses:
+ '200':
+ description: Updated realtime configuration
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RealtimeConfig'
+ '400':
+ description: Invalid configuration values
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /projects/{id}/realtime/stats:
+ get:
+ tags:
+ - Realtime
+ summary: Get realtime statistics for a project
+ description: Returns realtime usage statistics including connection counts and subscribed tables.
+ operationId: getRealtimeStats
+ security:
+ - UserToken: []
+ - ProjectAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/ProjectId'
+ responses:
+ '200':
+ description: Realtime statistics
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RealtimeStats'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /storage/{bucketName}:
+ get:
+ tags:
+ - Storage Objects
+ summary: List objects in a bucket
+ operationId: listStorageObjects
+ security:
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ - name: prefix
+ in: query
+ description: Filter objects by path prefix
+ schema:
+ type: string
+ - name: limit
+ in: query
+ description: Maximum objects to return
+ schema:
+ type: integer
+ default: 50
+ maximum: 1000
+ - name: cursor
+ in: query
+ description: Pagination cursor
+ schema:
+ type: string
+ responses:
+ '200':
+ description: List of objects
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageListResponse'
+ '403':
+ description: Access denied by storage policy
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ /locks/{key}/lease:
+ post:
+ tags:
+ - Locks
+ summary: Acquire a project lock
+ description: |
+ Acquires a project-scoped lease using the project embedded in the service-role key.
+ The caller must hold the `locks.manage` permission. Repeating the request with the
+ same lock token is idempotent and resets that lease to the requested TTL. A different
+ live owner receives `409 lock_held`; a caller whose own lease already lapsed receives
+ `409 lock_ownership_lost`.
+ operationId: acquireProjectLock
+ security:
+ - ServiceRoleKey: []
+ parameters:
+ - $ref: '#/components/parameters/LockKey'
+ - $ref: '#/components/parameters/LockToken'
+ - $ref: '#/components/parameters/LockRequestId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectLockLeaseRequest'
+ responses:
+ '201':
+ description: Lease acquired
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectLockLease'
+ '400':
+ description: Invalid lock key, token, or TTL
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Missing or invalid credentials
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: A non-service credential was supplied or the service key lacks `locks.manage`
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: |
+ The lock is held by another live lease (`lock_held`), or the caller's own lease
+ lapsed and is not yet reclaimable (`lock_ownership_lost`).
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Project lock request limit exceeded
+ headers:
+ Retry-After:
+ description: Seconds until the current fixed-minute window ends.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Lock service unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ patch:
+ tags:
+ - Locks
+ summary: Renew a project lock
+ description: |
+ Renews a lease owned by the supplied lock token. The request must arrive
+ more than one second before `expires_at`; this safety margin prevents
+ clock skew between regional API instances from resurrecting an expired
+ lease.
+ operationId: renewProjectLock
+ security:
+ - ServiceRoleKey: []
+ parameters:
+ - $ref: '#/components/parameters/LockKey'
+ - $ref: '#/components/parameters/LockToken'
+ - $ref: '#/components/parameters/LockRequestId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectLockLeaseRequest'
+ responses:
+ '200':
+ description: Lease renewed
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectLockLease'
+ '400':
+ description: Invalid lock key, token, or TTL
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Missing or invalid credentials
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: A non-service credential was supplied or the service key lacks `locks.manage`
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: The lease expired or is owned by another token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Project lock request limit exceeded
+ headers:
+ Retry-After:
+ description: Seconds until the current fixed-minute window ends.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Lock service unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ delete:
+ tags:
+ - Locks
+ summary: Release a project lock
+ description: Releases a lease only when the supplied lock token still owns it.
+ operationId: releaseProjectLock
+ security:
+ - ServiceRoleKey: []
+ parameters:
+ - $ref: '#/components/parameters/LockKey'
+ - $ref: '#/components/parameters/LockToken'
+ - $ref: '#/components/parameters/LockRequestId'
+ responses:
+ '204':
+ description: Lease released
+ '400':
+ description: Invalid lock key or token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Missing or invalid credentials
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: A non-service credential was supplied or the service key lacks `locks.manage`
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: The lease is owned by another token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Project lock request limit exceeded
+ headers:
+ Retry-After:
+ description: Seconds until the current fixed-minute window ends.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Lock service unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /locks/{key}:
+ get:
+ tags:
+ - Locks
+ summary: Read a project lock
+ description: |
+ Reports whether the lock is currently held, when its lease expires, and the
+ holder's fencing token. `held` follows takeover eligibility rather than raw
+ expiry, so `held: false` means an acquire would succeed now. No lock token is
+ required, making this usable for monitoring and recovery.
+ operationId: getProjectLock
+ security:
+ - ServiceRoleKey: []
+ parameters:
+ - $ref: '#/components/parameters/LockKey'
+ - $ref: '#/components/parameters/LockRequestId'
+ responses:
+ '200':
+ description: Current lock state
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ProjectLockState'
+ '400':
+ description: Invalid lock key
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Missing or invalid credentials
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: A non-service credential was supplied or the service key lacks `locks.manage`
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Project lock request limit exceeded
+ headers:
+ Retry-After:
+ description: Seconds until the current fixed-minute window ends.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Lock service unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ delete:
+ tags:
+ - Locks
+ summary: Force release a project lock
+ description: |
+ Drops the lease whatever token holds it, for recovering a lock whose holder
+ died without releasing. Use `DELETE /locks/{key}/lease` for normal release.
+
+ This breaks mutual exclusion by itself: the previous holder keeps working
+ until its own renewal fails. Guard the protected resource with the lease's
+ `fencing_token`, which the next acquisition raises, so a write from the
+ displaced holder can be rejected. Succeeds when the lock is already absent.
+ operationId: forceReleaseProjectLock
+ security:
+ - ServiceRoleKey: []
+ parameters:
+ - $ref: '#/components/parameters/LockKey'
+ - $ref: '#/components/parameters/LockRequestId'
+ responses:
+ '204':
+ description: Lock released
+ '400':
+ description: Invalid lock key
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '401':
+ description: Missing or invalid credentials
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: A non-service credential was supplied or the service key lacks `locks.manage`
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '429':
+ description: Project lock request limit exceeded
+ headers:
+ Retry-After:
+ description: Seconds until the current fixed-minute window ends.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '503':
+ description: Lock service unavailable
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ /storage/{bucketName}/move:
+ post:
+ tags:
+ - Storage Objects
+ summary: Move/rename an object
+ operationId: moveStorageObject
+ security:
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageMoveRequest'
+ responses:
+ '200':
+ description: Object moved
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageObject'
+ '403':
+ description: Access denied by storage policy
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ /storage/{bucketName}/copy:
+ post:
+ tags:
+ - Storage Objects
+ summary: Copy an object
+ operationId: copyStorageObject
+ security:
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageCopyRequest'
+ responses:
+ '201':
+ description: Object copied
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageObject'
+ '403':
+ description: Access denied by storage policy
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ /storage/{bucketName}/{path}:
+ post:
+ tags:
+ - Storage Objects
+ summary: Upload a file or create resumable session
+ description: |
+ Unified endpoint for file uploads. Behavior depends on Content-Type and headers:
+
+ **Simple Upload (multipart/form-data):**
+ Upload a complete file in a single request. Best for files under 100MB.
+
+ **Create Resumable Session (application/json):**
+ Create a session for chunked uploads. Best for large files or unreliable networks.
+ Requires: `Content-Type: application/json` with body `{"filename": "...", "content_type": "...", "total_size": ...}`
+
+ **Complete Resumable Session:**
+ Complete a session after all parts are uploaded.
+ Requires: `X-Upload-Session` header with session ID and `X-Upload-Complete: true` header.
+
+ **Resumable Session Ownership:**
+ A session created with a user access token remains bound to that user. A session
+ created with an anon key remains bound to that exact anon key. Reuse the same
+ identity or anon key for part uploads, status, completion, and abort requests;
+ an ownership mismatch returns `404`.
+ operationId: uploadStorageObject
+ security:
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ - name: path
+ in: path
+ required: true
+ description: Object path within bucket
+ schema:
+ type: string
+ - name: X-Upload-Session
+ in: header
+ required: false
+ description: Upload session ID (for completing resumable uploads)
+ schema:
+ type: string
+ - name: X-Upload-Complete
+ in: header
+ required: false
+ description: Set to "true" to complete a resumable upload session
+ schema:
+ type: string
+ enum:
+ - 'true'
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ required:
+ - file
+ properties:
+ file:
+ type: string
+ format: binary
+ description: File to upload (simple upload)
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateUploadSessionRequest'
+ responses:
+ '200':
+ description: Resumable upload completed (when X-Upload-Complete=true)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CompleteUploadSessionResponse'
+ '201':
+ description: File uploaded or session created
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/StorageObject'
+ - $ref: '#/components/schemas/CreateUploadSessionResponse'
+ '400':
+ description: |
+ Bad request. This can occur when:
+ - MIME type is not in the bucket's allowed_mime_types list
+ - File exceeds the bucket's configured file_size_limit
+ - File exceeds the global maximum upload size (5GB)
+ - Invalid request body or missing required fields
+ '403':
+ description: Access denied by storage policy
+ '404':
+ description: Resumable upload session not found or not owned by this credential
+ '413':
+ description: |
+ File size exceeds plan-based limits. This occurs when:
+ - File exceeds the plan-based maximum file size (FREE or PRO tier)
+ - The account on the FREE plan holds its file-storage allowance.
+ Enforcement is eventual: uploads are accepted until Volcano's
+ next allowance check sees the account at its allowance
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ put:
+ tags:
+ - Storage Objects
+ summary: Upload a part of a resumable upload
+ description: |
+ Upload a single part of a resumable upload session.
+
+ **Requirements:**
+ - Part numbers start at 1
+ - All parts except the last must be at least 5MB
+ - Maximum part size is 25MB
+ - Parts can be uploaded in any order
+ - Re-uploading a part overwrites the previous upload
+ - Anonymous sessions must reuse the exact anon key that created the session
+ operationId: uploadPart
+ security:
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ - name: path
+ in: path
+ required: true
+ description: Object path within bucket
+ schema:
+ type: string
+ - name: X-Upload-Session
+ in: header
+ required: true
+ description: Upload session ID
+ schema:
+ type: string
+ - name: X-Part-Number
+ in: header
+ required: true
+ description: Part number (1 to 10000)
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 10000
+ requestBody:
+ required: true
+ content:
+ application/octet-stream:
+ schema:
+ type: string
+ format: binary
+ responses:
+ '200':
+ description: Part uploaded
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UploadSessionPart'
+ '400':
+ description: Invalid part number or part data
+ '403':
+ description: Access denied
+ '404':
+ description: Session not found or not owned by this credential
+ get:
+ tags:
+ - Storage Objects
+ summary: Download a file or get upload session status
+ description: |
+ Download a file, or get the status of a resumable upload session.
+
+ **File Download (default):**
+ Downloads the file at the specified path.
+
+ **Session Status (with X-Upload-Session header):**
+ Returns the status of a resumable upload session, including which parts have been uploaded.
+ Anonymous sessions must reuse the exact anon key that created the session.
+ operationId: downloadStorageObject
+ security:
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ - name: path
+ in: path
+ required: true
+ description: Object path within bucket
+ schema:
+ type: string
+ - name: Range
+ in: header
+ required: false
+ description: |
+ HTTP Range header for partial downloads.
+ Format: bytes=start-end or bytes=start-
+ Examples: bytes=0-1023, bytes=1000-
+ schema:
+ type: string
+ pattern: ^bytes=\d+-\d*$
+ - name: X-Upload-Session
+ in: header
+ required: false
+ description: Upload session ID (to get session status instead of downloading)
+ schema:
+ type: string
+ responses:
+ '200':
+ description: File content or session status
+ headers:
+ Content-Type:
+ schema:
+ type: string
+ Content-Length:
+ schema:
+ type: integer
+ ETag:
+ schema:
+ type: string
+ content:
+ application/octet-stream:
+ schema:
+ type: string
+ format: binary
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UploadSessionStatusResponse'
+ '206':
+ description: Partial content (range request)
+ '400':
+ description: Invalid Range header format
+ '403':
+ description: Access denied by storage policy
+ '404':
+ description: Object or session not found, or session not owned by this credential
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ delete:
+ tags:
+ - Storage Objects
+ summary: Delete a file or abort upload session
+ description: |
+ Delete a file, or abort a resumable upload session.
+
+ **File Delete (default):**
+ Deletes the file at the specified path.
+
+ **Abort Session (with X-Upload-Session header):**
+ Aborts a resumable upload session and cleans up any uploaded parts.
+ Anonymous sessions must reuse the exact anon key that created the session.
+ operationId: deleteStorageObject
+ security:
+ - AnonKey: []
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ - name: path
+ in: path
+ required: true
+ description: Object path within bucket
+ schema:
+ type: string
+ - name: X-Upload-Session
+ in: header
+ required: false
+ description: Upload session ID (to abort session instead of deleting file)
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Object deleted or session aborted
+ '403':
+ description: Access denied by storage policy
+ '404':
+ description: Object or session not found, or session not owned by this credential
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ /storage/{bucketName}/{path}/visibility:
+ patch:
+ tags:
+ - Storage Objects
+ summary: Update file visibility (public/private)
+ description: |
+ Change whether a file is publicly accessible. Only the file owner or a service key can change visibility.
+ If the bucket defines UPDATE policies, the owner must also satisfy one of them.
+
+ - Public files can be downloaded with just an anon key (no user authentication required)
+ - Private files (default) require authentication and must pass policy checks
+ - All downloads go through the Volcano API - there is no direct access to the underlying store
+ operationId: updateStorageObjectVisibility
+ security:
+ - ServiceRoleKey: []
+ - AuthUserAccessToken: []
+ parameters:
+ - $ref: '#/components/parameters/BucketName'
+ - name: path
+ in: path
+ required: true
+ description: Object path within bucket
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageVisibilityRequest'
+ responses:
+ '200':
+ description: Visibility updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StorageObject'
+ '403':
+ description: Not the file owner or denied by the bucket's UPDATE policies
+ '404':
+ description: Object not found
+ /public/{projectId}/{bucketName}/{path}:
+ get:
+ tags:
+ - Storage Objects
+ summary: Download a public file (no authentication required)
+ description: |
+ Download a file that has been marked as public. This endpoint requires NO authentication.
+
+ **Access Requirements:**
+ - The file must have `is_public: true` set via the visibility endpoint
+ - Private files will return 403 Forbidden
+
+ **Use Cases:**
+ - Shareable public URLs for profile pictures, public documents, etc.
+ - Embedding public files on external websites
+ - Direct linking without requiring SDK or authentication
+
+ **URL Format:**
+ ```
+ GET /public/{projectId}/{bucketName}/{path}
+ ```
+
+ **Example:**
+ ```
+ https://api.volcano.dev/public/abc123/avatars/user-photo.jpg
+ ```
+
+ **CORS:**
+ This endpoint allows all origins since the file is already public.
+ operationId: downloadPublicFile
+ parameters:
+ - name: projectId
+ in: path
+ required: true
+ description: Project ID
+ schema:
+ type: string
+ format: uuid
+ - $ref: '#/components/parameters/BucketName'
+ - name: path
+ in: path
+ required: true
+ description: Object path within bucket
+ schema:
+ type: string
+ responses:
+ '200':
+ description: File content
+ content:
+ '*/*':
+ schema:
+ type: string
+ format: binary
+ '206':
+ description: Partial content (range request)
+ '404':
+ description: Not found (file doesn't exist or is not public)
+ '429':
+ $ref: '#/components/responses/BandwidthCapExceeded'
+ /health:
+ get:
+ tags:
+ - System
+ summary: Health check endpoint
+ description: Returns server health status. Used for load balancer and monitoring checks.
+ operationId: healthCheck
+ responses:
+ '200':
+ description: Server is healthy
+ content:
+ text/plain:
+ schema:
+ type: string
+ example: OK
+ /openapi.json:
+ get:
+ tags:
+ - System
+ summary: Fetch the OpenAPI specification as JSON
+ description: |
+ Returns this specification as a self-contained JSON document, with every
+ reference resolved. It is generated from the same document the server
+ validates requests against, so a client generated from it cannot
+ describe a different API than the one that answers.
+
+ No credential is required: a client generator fetches this by URL before
+ its user has a token, and every path here is already published in the
+ API reference.
+
+ The response carries a strong `ETag`; send it back as `If-None-Match` to
+ get `304 Not Modified` instead of the whole document.
+ operationId: getOpenAPISpecJSON
+ security: []
+ parameters:
+ - $ref: '#/components/parameters/IfNoneMatch'
+ responses:
+ '200':
+ description: The OpenAPI specification
+ headers:
+ ETag:
+ $ref: '#/components/headers/ETag'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OpenAPISpecDocument'
+ '304':
+ $ref: '#/components/responses/OpenAPISpecNotModified'
+ '429':
+ $ref: '#/components/responses/OpenAPISpecThrottled'
+ head:
+ tags:
+ - System
+ summary: Check the JSON OpenAPI specification
+ description: |
+ The headers `GET /openapi.json` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+ operationId: headOpenAPISpecJSON
+ security: []
+ parameters:
+ - $ref: '#/components/parameters/IfNoneMatch'
+ responses:
+ '200':
+ $ref: '#/components/responses/OpenAPISpecHeaders'
+ '304':
+ $ref: '#/components/responses/OpenAPISpecNotModified'
+ '429':
+ $ref: '#/components/responses/OpenAPISpecThrottled'
+ /openapi.yaml:
+ get:
+ tags:
+ - System
+ summary: Fetch the OpenAPI specification as YAML
+ description: |
+ The same document as `/openapi.json`, serialized as YAML for tools that
+ prefer it. See that operation for caching and authentication notes.
+ operationId: getOpenAPISpecYAML
+ security: []
+ parameters:
+ - $ref: '#/components/parameters/IfNoneMatch'
+ responses:
+ '200':
+ description: The OpenAPI specification
+ headers:
+ ETag:
+ $ref: '#/components/headers/ETag'
+ content:
+ application/yaml:
+ schema:
+ $ref: '#/components/schemas/OpenAPISpecDocument'
+ '304':
+ $ref: '#/components/responses/OpenAPISpecNotModified'
+ '429':
+ $ref: '#/components/responses/OpenAPISpecThrottled'
+ head:
+ tags:
+ - System
+ summary: Check the YAML OpenAPI specification
+ description: |
+ The headers `GET /openapi.yaml` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+ operationId: headOpenAPISpecYAML
+ security: []
+ parameters:
+ - $ref: '#/components/parameters/IfNoneMatch'
+ responses:
+ '200':
+ $ref: '#/components/responses/OpenAPISpecHeaders'
+ '304':
+ $ref: '#/components/responses/OpenAPISpecNotModified'
+ '429':
+ $ref: '#/components/responses/OpenAPISpecThrottled'
+ /mcp:
+ post:
+ tags:
+ - System
+ summary: Model Context Protocol endpoint
+ description: |
+ Streamable-HTTP MCP endpoint: one JSON-RPC 2.0 object per request, one
+ response per request. There is no server-to-client stream, so a `GET`
+ returns `405`, and a batched array is rejected.
+
+ Authenticated with a **project access token**. The endpoint takes its
+ project from the credential, so a platform token is refused with `403` —
+ it names no project, and letting a tool argument choose one would hand an
+ agent its own blast radius.
+
+ Scope carries over from the REST API. A `read_only` token is not offered
+ mutating tools or credential-returning reads, and is refused if it calls
+ one anyway. Revoking the token ends MCP access on the same path it ends
+ API access.
+
+ Methods: `initialize`, `notifications/initialized`, `ping`,
+ `tools/list`, `tools/call`. See the
+ [MCP guide](https://docs.volcano.dev/platform/interfaces/mcp) for the
+ tool surface and client configuration.
+ operationId: callMCP
+ security:
+ - ProjectAccessToken: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ description: A JSON-RPC 2.0 request object.
+ required:
+ - jsonrpc
+ - method
+ properties:
+ jsonrpc:
+ type: string
+ enum:
+ - '2.0'
+ method:
+ type: string
+ description: The MCP method to call.
+ id:
+ description: |
+ Request identifier, echoed verbatim. Omit it to send a
+ notification, which is answered with `202` and no body.
+ oneOf:
+ - type: string
+ - type: integer
+ params:
+ type: object
+ additionalProperties: true
+ responses:
+ '200':
+ description: A JSON-RPC response object
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - jsonrpc
+ - id
+ properties:
+ jsonrpc:
+ type: string
+ enum:
+ - '2.0'
+ id:
+ description: Echoes the request's id. Null when the request could not be read well enough to determine one.
+ nullable: true
+ oneOf:
+ - type: string
+ - type: integer
+ result:
+ type: object
+ additionalProperties: true
+ error:
+ type: object
+ required:
+ - code
+ - message
+ properties:
+ code:
+ type: integer
+ message:
+ type: string
+ '202':
+ description: A notification was accepted; there is no body
+ '401':
+ description: Not authenticated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: |
+ The credential is valid but may not use this endpoint — most often a
+ platform token, which names no project.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '413':
+ description: The JSON-RPC frame exceeds the 1 MiB limit
+components:
+ headers:
+ ETag:
+ description: |
+ Strong validator for the returned document. Send it back as `If-None-Match`
+ to revalidate without transferring the document again.
+ required: true
+ schema:
+ type: string
+ example: '"9f2c1e0b5a"'
+ DatabaseQueryProxyMs:
+ description: |
+ Milliseconds Volcano spent before running the query, counted from the
+ request arriving: authenticating the caller, validating the request,
+ resolving the database, and building the SQL. Does not include query
+ execution. Also present when the query itself failed.
+ schema:
+ type: integer
+ minimum: 0
+ DatabaseQueryProxyHandlerMs:
+ description: |
+ The part of `X-Volcano-Proxy-Ms` spent in the query endpoint itself.
+ Subtract it from `X-Volcano-Proxy-Ms` to see what authentication and
+ request validation cost. Also present when the query itself failed.
+ schema:
+ type: integer
+ minimum: 0
+ DatabaseQueryComputeMs:
+ description: |
+ Milliseconds the database took to run the query and return its rows.
+ Does not include Volcano's preparation. Also present when the query
+ itself failed.
+ schema:
+ type: integer
+ minimum: 0
+ securitySchemes:
+ AnonKey:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
+ description: |
+ Project-specific public key for frontend authentication.
+ Required for signup, signin, refresh, and logout endpoints.
+ Get from Project Settings → Authentication → Anon Keys.
+ Safe to expose in frontend code (scoped to project, limited permissions).
+ AuthUserAccessToken:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
+ description: |
+ Auth user access token obtained from signup/signin.
+ Used for authenticated function invocation and user profile access.
+ Functions invoked with access tokens receive user context in event.__volcano_auth.
+ Expires after configured lifetime (default: 1 hour).
+ ServiceRoleKey:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
+ description: |
+ Service role key for admin operations.
+ **WARNING:** Bypasses Row-Level Security - backend use only!
+ Create via POST /projects/{id}/service-keys.
+ Used for function invocation with full database access.
+ UserToken:
+ type: http
+ scheme: bearer
+ bearerFormat: opaque
+ description: |
+ Platform token, which acts on every project in your account.
+ Obtain one by running `volcano login`, or from the dashboard.
+
+ For CI, scripts, and agents, prefer a project access token
+ (`pt-`) instead: it reaches only the project it was created in,
+ so a leak does not expose the rest of your account. See the
+ ProjectAccessToken scheme.
+ ProjectAccessToken:
+ type: http
+ scheme: bearer
+ bearerFormat: opaque
+ description: |
+ Project access token, scoped to a single project. Starts with `pt-`.
+
+ Accepted on that project's control-plane routes and refused
+ everywhere else, including account-wide endpoints and any other
+ project. A `read_only` token additionally refuses mutations.
+
+ Create one with POST /projects/{id}/access-tokens using a platform
+ token. The secret is returned once and is not recoverable, so a
+ project access token cannot create, list, read, or revoke project
+ access tokens.
+ parameters:
+ BackupName:
+ name: backupName
+ in: path
+ required: true
+ schema:
+ type: string
+ minLength: 1
+ maxLength: 128
+ description: |
+ Backup name, unique within the database, exactly as returned by the list
+ endpoint.
+
+ Deliberately looser than the names you can create: a backup made by a
+ schedule is named for you, so reading or deleting one accepts any name a
+ backup can have.
+ BranchName:
+ name: branchName
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: ^[a-z0-9_]+$
+ maxLength: 64
+ description: Branch name (unique within the parent database, lowercase letters, numbers, and underscores only)
+ BucketName:
+ name: bucketName
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: ^[a-zA-Z0-9_-]+$
+ minLength: 1
+ maxLength: 64
+ description: Storage bucket name
+ Cursor:
+ name: cursor
+ in: query
+ required: false
+ schema:
+ type: string
+ description: |
+ Opaque keyset pagination cursor from a previous response's `next_cursor`
+ — pages forward. Mutually exclusive with `page` and `ending_before`;
+ combining them returns 400. When supplied, the request's `search` and
+ `limit` must match the values bound to the cursor or the request returns 400.
+ EndingBefore:
+ name: ending_before
+ in: query
+ required: false
+ schema:
+ type: string
+ description: |
+ Opaque keyset pagination cursor from a previous response's `prev_cursor`
+ — pages backward (the page immediately preceding this cursor). Mutually
+ exclusive with `page` and `cursor`; combining them returns 400. `search`
+ and `limit` must match the values bound to the cursor or the request
+ returns 400.
+ DatabaseName:
+ name: databaseName
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: ^[a-z0-9_]+$
+ maxLength: 64
+ description: Database name (unique within project, lowercase letters, numbers, and underscores only)
+ DeploymentId:
+ name: deploymentId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Frontend deployment ID
+ FrontendId:
+ name: frontendId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Frontend ID
+ FrontendFunctionRouteId:
+ name: routeId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Frontend Function route ID
+ FunctionId:
+ name: functionId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Function ID
+ DurableFunctionId:
+ name: functionId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Durable function ID, or its name within the project
+ DurableExecutionId:
+ name: executionId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Durable execution ID
+ Limit:
+ name: limit
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ maximum: 100
+ default: 10
+ description: Number of items per page (max 100)
+ LockKey:
+ name: key
+ in: path
+ required: true
+ description: Project-local lock name.
+ schema:
+ type: string
+ minLength: 1
+ maxLength: 128
+ pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
+ LockToken:
+ name: X-Volcano-Lock-Token
+ in: header
+ required: true
+ description: Opaque UUID generated once by the caller and retained for the lease lifetime.
+ schema:
+ type: string
+ format: uuid
+ LockRequestId:
+ name: X-Volcano-Request-Id
+ in: header
+ required: true
+ description: |
+ UUID correlating this request across client and server logs. Repeat safety comes from
+ the lock token, so a retry under a reused request ID still counts against the quota.
+ schema:
+ type: string
+ format: uuid
+ DeploymentOperation:
+ name: operation
+ in: query
+ required: false
+ description: Restrict a deployment feed to one kind of operation.
+ schema:
+ type: string
+ enum:
+ - deploy
+ - redeploy
+ - update
+ - delete
+ DeploymentOwnerId:
+ name: owner_id
+ in: query
+ required: false
+ description: |
+ The user who owns the projects whose deployments to return
+ (`projects.user_id`). This is ownership, not the actor that started the
+ deployment — see `initiated_by_user_id` for that. Not a UUID: platform
+ user ids are opaque strings.
+ schema:
+ type: string
+ maxLength: 255
+ DeploymentOrder:
+ name: order
+ in: query
+ required: false
+ description: |
+ Sort key and direction. `created_at.desc` (default) is the feed order.
+ `completed_at.asc` orders finished attempts by completion, oldest first,
+ and excludes attempts that never completed.
+ schema:
+ type: string
+ enum:
+ - created_at.desc
+ - completed_at.asc
+ default: created_at.desc
+ DeploymentResourceType:
+ name: resource_type
+ in: query
+ required: false
+ description: |
+ Restrict a deployment feed to a single resource type. Omit to return
+ both Function and Frontend deployments.
+ schema:
+ type: string
+ enum:
+ - function
+ - frontend
+ DeploymentStatus:
+ name: status
+ in: query
+ required: false
+ description: Restrict a deployment feed to attempts in one status.
+ schema:
+ type: string
+ enum:
+ - queued
+ - provisioning
+ - active
+ - degraded
+ - failed
+ - superseded
+ - deleting
+ - deleted
+ Offset:
+ name: offset
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 0
+ default: 0
+ description: |
+ Bounded row offset past the keyset anchor named by `cursor` (forward) or
+ `ending_before` (backward) — the hybrid jump. Seek to the anchor, then
+ skip this many rows within. Used for numbered jump-to-page: from the
+ current page, seek to its next/prev cursor and offset the remaining
+ pages. Only honored on the cursor pagination path; ignored otherwise.
+ Page:
+ name: page
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ description: |
+ Page number (1-indexed) for offset pagination. Declares no schema
+ default so the request validator does not inject one: handlers that omit
+ `page` see it unset (nil) and default to 1 in code, while cursor-first
+ endpoints (e.g. the project deployments feed) can detect its absence to
+ stay in keyset/search mode. Supplying `page` selects offset pagination.
+ ProjectId:
+ name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Project ID
+ TokenId:
+ name: tokenId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Project access token ID
+ Search:
+ name: search
+ in: query
+ required: false
+ schema:
+ type: string
+ maxLength: 256
+ description: |
+ Case-insensitive substring match on the resource `name`. See the
+ endpoint description for supported pagination modes.
+ RestoreId:
+ name: restoreId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Database restore ID
+ SchedulerId:
+ name: schedulerId
+ in: path
+ required: true
+ schema:
+ type: string
+ format: uuid
+ description: Function scheduler ID
+ VariableName:
+ name: name
+ in: path
+ required: true
+ schema:
+ type: string
+ minLength: 1
+ maxLength: 256
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ description: Variable name
+ IfNoneMatch:
+ name: If-None-Match
+ in: header
+ required: false
+ description: |
+ Entity tag from an earlier response, returning `304 Not Modified` while it
+ still matches. Accepts the full condition: `*`, a comma-separated list, and
+ weak tags of the form `W/"tag"`.
+ schema:
+ type: string
+ example: '"9f2c1e0b5a"'
+ responses:
+ OpenAPISpecThrottled:
+ description: |
+ Too many requests for the specification from one address. The document
+ carries an `ETag`; revalidate with `If-None-Match` rather than
+ re-fetching it.
+ headers:
+ Retry-After:
+ description: Seconds to wait before requesting the specification again.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ BandwidthCapExceeded:
+ description: |
+ The platform user exceeded their billing-cycle bandwidth allowance (aggregate
+ ingress + egress across owned projects). Enforcement is eventual:
+ requests are rejected until the allowance increases or the next
+ anniversary cycle begins.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ DatabaseQueryCapExceeded:
+ description: |
+ The query was rejected by a billing-cycle allowance: either the owning
+ platform user's bandwidth allowance (aggregate ingress + egress across
+ owned projects) or their database-request allowance. Enforcement is
+ eventual: queries are rejected until the allowance increases or the
+ next anniversary cycle begins. The error message identifies the resource.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ DatabaseBranchQueryUnavailable:
+ description: |
+ The branch exists but cannot serve queries: it is still provisioning,
+ being reset, expired, or its parent is being restored. Distinct from
+ `404` so a caller waiting on a branch can tell it apart from a typo.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ OpenAPISpecNotModified:
+ description: |
+ The specification still matches the supplied `If-None-Match`, so no body
+ is returned.
+ headers:
+ ETag:
+ $ref: '#/components/headers/ETag'
+ OpenAPISpecHeaders:
+ description: |
+ The headers a `GET` would return, without the document. The declared
+ `ETag` is the one to revalidate against.
+ headers:
+ ETag:
+ $ref: '#/components/headers/ETag'
+ schemas:
+ SandboxPreset:
+ type: object
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ runtime:
+ type: string
+ version:
+ type: string
+ memory_mb:
+ type: integer
+ enum:
+ - 1024
+ - 2048
+ regions:
+ type: array
+ items:
+ type: string
+ required:
+ - id
+ - runtime
+ - version
+ - memory_mb
+ - regions
+ SandboxTemplate:
+ type: object
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ pattern: ^[a-z][a-z0-9-]{0,62}$
+ preset:
+ type: string
+ memory_mb:
+ type: integer
+ status:
+ type: string
+ enum:
+ - ready
+ - unavailable
+ - deleting
+ created_at:
+ type: string
+ format: date-time
+ required:
+ - id
+ - project_id
+ - name
+ - status
+ - created_at
+ CreateSandboxTemplateRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ name:
+ type: string
+ pattern: ^[a-z][a-z0-9-]{0,62}$
+ preset:
+ type: string
+ enum:
+ - python3.12
+ - node22
+ memory_mb:
+ type: integer
+ enum:
+ - 1024
+ - 2048
+ default: 1024
+ required:
+ - name
+ - preset
+ UpdateSandboxTemplateRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ name:
+ type: string
+ pattern: ^[a-z][a-z0-9-]{0,62}$
+ required:
+ - name
+ SandboxSession:
+ type: object
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ sandbox_id:
+ type: string
+ format: uuid
+ state:
+ type: string
+ enum:
+ - starting
+ - running
+ - suspending
+ - suspended
+ - resuming
+ - terminating
+ - terminated
+ - unknown
+ desired_state:
+ type: string
+ enum:
+ - running
+ - suspended
+ - terminated
+ region:
+ type: string
+ memory_mb:
+ type: integer
+ created_at:
+ type: string
+ format: date-time
+ started_at:
+ type: string
+ format: date-time
+ expires_at:
+ type: string
+ format: date-time
+ required:
+ - id
+ - project_id
+ - sandbox_id
+ - state
+ - desired_state
+ - region
+ - memory_mb
+ - created_at
+ - expires_at
+ CreateSandboxSessionRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ preset:
+ type: string
+ enum:
+ - python3.12
+ - node22
+ sandbox_id:
+ type: string
+ format: uuid
+ memory_mb:
+ type: integer
+ enum:
+ - 1024
+ - 2048
+ region:
+ type: string
+ pattern: ^aws-[a-z0-9-]+$
+ max_duration_seconds:
+ type: integer
+ minimum: 30
+ maximum: 28800
+ default: 3600
+ idle_timeout_seconds:
+ type: integer
+ minimum: 0
+ maximum: 28800
+ default: 0
+ required:
+ - region
+ oneOf:
+ - required:
+ - preset
+ not:
+ required:
+ - sandbox_id
+ - required:
+ - sandbox_id
+ not:
+ required:
+ - preset
+ SandboxCommandRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ command:
+ type: string
+ minLength: 1
+ maxLength: 65536
+ timeout_seconds:
+ type: integer
+ minimum: 1
+ maximum: 3600
+ default: 60
+ environment:
+ type: object
+ additionalProperties:
+ type: string
+ maxProperties: 64
+ required:
+ - command
+ SandboxExecutionRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ preset:
+ type: string
+ enum:
+ - python3.12
+ - node22
+ sandbox_id:
+ type: string
+ format: uuid
+ memory_mb:
+ type: integer
+ enum:
+ - 1024
+ - 2048
+ region:
+ type: string
+ pattern: ^aws-[a-z0-9-]+$
+ command:
+ type: string
+ minLength: 1
+ maxLength: 65536
+ timeout_seconds:
+ type: integer
+ minimum: 1
+ maximum: 60
+ default: 60
+ environment:
+ type: object
+ additionalProperties:
+ type: string
+ maxProperties: 64
+ required:
+ - region
+ - command
+ oneOf:
+ - required:
+ - preset
+ not:
+ required:
+ - sandbox_id
+ - required:
+ - sandbox_id
+ not:
+ required:
+ - preset
+ SandboxCommandResult:
+ type: object
+ additionalProperties: false
+ properties:
+ stdout:
+ type: string
+ stderr:
+ type: string
+ exit_code:
+ type: integer
+ stdout_truncated:
+ type: boolean
+ stderr_truncated:
+ type: boolean
+ timed_out:
+ type: boolean
+ required:
+ - stdout
+ - stderr
+ - exit_code
+ - stdout_truncated
+ - stderr_truncated
+ - timed_out
+ SandboxExecutionResult:
+ type: object
+ additionalProperties: false
+ properties:
+ stdout:
+ type: string
+ stderr:
+ type: string
+ exit_code:
+ type: integer
+ stdout_truncated:
+ type: boolean
+ stderr_truncated:
+ type: boolean
+ timed_out:
+ type: boolean
+ session_id:
+ type: string
+ format: uuid
+ region:
+ type: string
+ duration_ms:
+ type: integer
+ format: int64
+ minimum: 0
+ required:
+ - stdout
+ - stderr
+ - exit_code
+ - stdout_truncated
+ - stderr_truncated
+ - timed_out
+ - session_id
+ - region
+ - duration_ms
+ SandboxFileWriteRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ path:
+ type: string
+ minLength: 1
+ maxLength: 4096
+ data:
+ type: string
+ format: byte
+ maxLength: 11184812
+ required:
+ - path
+ - data
+ SandboxFileReadRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ path:
+ type: string
+ minLength: 1
+ maxLength: 4096
+ required:
+ - path
+ SandboxFileResult:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: string
+ format: byte
+ required:
+ - data
+ SandboxSubjectGrantRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ expires_at:
+ type: string
+ format: date-time
+ required:
+ - expires_at
+ SandboxAccessRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ port:
+ type: integer
+ minimum: 1
+ maximum: 65532
+ expires_in_seconds:
+ type: integer
+ minimum: 1
+ maximum: 300
+ default: 300
+ required:
+ - port
+ SandboxAccess:
+ type: object
+ additionalProperties: false
+ properties:
+ url:
+ type: string
+ format: uri
+ token:
+ type: string
+ expires_at:
+ type: string
+ format: date-time
+ required:
+ - url
+ - token
+ - expires_at
+ SandboxDeployment:
+ type: object
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ format: uuid
+ status:
+ type: string
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ required:
+ - id
+ - status
+ - created_at
+ - updated_at
+ SandboxPagination:
+ type: object
+ additionalProperties: false
+ properties:
+ limit:
+ type: integer
+ has_more:
+ type: boolean
+ next_cursor:
+ type: string
+ required:
+ - limit
+ - has_more
+ SandboxTemplatePage:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/SandboxTemplate'
+ pagination:
+ $ref: '#/components/schemas/SandboxPagination'
+ required:
+ - data
+ - pagination
+ SandboxSessionPage:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/SandboxSession'
+ pagination:
+ $ref: '#/components/schemas/SandboxPagination'
+ required:
+ - data
+ - pagination
+ SandboxDeploymentPage:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/SandboxDeployment'
+ pagination:
+ $ref: '#/components/schemas/SandboxPagination'
+ required:
+ - data
+ - pagination
+ SandboxPresetList:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/SandboxPreset'
+ required:
+ - data
+ SandboxCapacity:
+ type: object
+ additionalProperties: false
+ properties:
+ region:
+ type: string
+ allocated_memory_mb:
+ type: integer
+ format: int64
+ minimum: 0
+ required:
+ - region
+ - allocated_memory_mb
+ SandboxCapacityList:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/SandboxCapacity'
+ required:
+ - data
+ PublishSandboxPresetRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ format: uuid
+ preset:
+ type: string
+ enum:
+ - python3.12
+ - node22
+ memory_mb:
+ type: integer
+ enum:
+ - 1024
+ - 2048
+ deployment_id:
+ type: string
+ format: uuid
+ required:
+ - id
+ - preset
+ - memory_mb
+ - deployment_id
AnonKey:
type: object
properties:
@@ -15146,6 +17560,20 @@ components:
Frontend:
type: object
properties:
+ variable_scope:
+ type: string
+ enum:
+ - all
+ - shared
+ - scoped
+ description: All preserves access to all project variables. Shared includes the project frontend shared-variable list. Scoped includes only explicitly declared variables in builds and runtime. Omission preserves the stored selection.
+ declared_variables:
+ type: array
+ uniqueItems: true
+ items:
+ type: string
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ description: Names selected when variable_scope is scoped. Missing declared values reject deployment. Omission preserves the stored list; an empty list clears it.
id:
type: string
format: uuid
@@ -15229,6 +17657,72 @@ components:
- deployed_regions
- created_at
- updated_at
+ FrontendFunctionRoute:
+ type: object
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ frontend_id:
+ type: string
+ format: uuid
+ function_id:
+ type: string
+ format: uuid
+ path_prefix:
+ type: string
+ minLength: 2
+ maxLength: 512
+ pattern: ^/[^?#\\]*[^/?#\\]$
+ strip_prefix:
+ type: boolean
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ required:
+ - id
+ - project_id
+ - frontend_id
+ - function_id
+ - path_prefix
+ - strip_prefix
+ - created_at
+ - updated_at
+ FrontendFunctionRouteList:
+ type: object
+ additionalProperties: false
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/FrontendFunctionRoute'
+ required:
+ - data
+ CreateFrontendFunctionRouteRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ function_id:
+ type: string
+ format: uuid
+ path_prefix:
+ type: string
+ minLength: 2
+ maxLength: 512
+ pattern: ^/[^?#\\]*[^/?#\\]$
+ strip_prefix:
+ type: boolean
+ default: false
+ required:
+ - function_id
+ - path_prefix
FrontendCustomDomainResponse:
type: object
properties:
@@ -15488,6 +17982,217 @@ components:
- total_requests
- total_errors
- total_page_views
+ ProjectAccessToken:
+ type: object
+ description: |
+ A project access token: a control-plane credential bound to a single
+ project. Unlike a platform token, which acts on every project its owner
+ has, this one is limited to the project it was created in.
+
+ The secret itself is never returned here. Only its hash is stored, so
+ the plaintext exists solely in the response to the create call.
+ properties:
+ id:
+ type: string
+ format: uuid
+ project_id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ description: Unique per project.
+ token_prefix:
+ type: string
+ description: First 12 characters of the secret, for recognising a token in a list.
+ scope:
+ $ref: '#/components/schemas/ProjectAccessTokenScope'
+ status:
+ type: string
+ enum:
+ - active
+ - revoked
+ - expired
+ description: |
+ `revoked` means the token was deliberately revoked, by you or by the
+ deletion of its project. `expired` means it simply reached
+ `expires_at`; nothing was taken away. Both are refused, and both keep
+ their record so a token's name, prefix, last use, and request history
+ remain available after a leak.
+
+ A token revoked before its expiry passed stays `revoked`, because
+ that is the fact worth keeping.
+ token_source:
+ type: string
+ enum:
+ - api
+ - cli
+ - dashboard
+ description: What created the token.
+ expires_at:
+ type: string
+ format: date-time
+ nullable: true
+ description: Absent for a token that does not expire.
+ last_used_at:
+ type: string
+ format: date-time
+ nullable: true
+ description: Updated at most once every few minutes, so it may lag slightly.
+ created_at:
+ type: string
+ format: date-time
+ all_time_requests:
+ type: integer
+ format: int64
+ description: Requests authenticated with this token since it was created.
+ required:
+ - id
+ - project_id
+ - name
+ - token_prefix
+ - scope
+ - status
+ - token_source
+ - created_at
+ - all_time_requests
+ ProjectAccessTokenScope:
+ type: string
+ description: |
+ What a project access token may do within its project.
+
+ `full` is everything you can do to that one project, up to and including
+ deleting it. It cannot manage access tokens, so a leaked token cannot
+ mint a replacement or erase the record of its own use, but for a CI or
+ agent credential that only deploys, prefer `read_only` where the job
+ allows it.
+
+ `read_only` refuses mutations. It is enforced by route classification
+ rather than HTTP method, so the log and metrics query endpoints remain
+ available even though they are POST requests that carry body filters.
+
+ `read_only` also refuses the reads that return a credential — service
+ keys, anon keys, variable values, and database connection strings. Those
+ grant write access over the project's data and keep working after the
+ token that fetched them is revoked, so returning one to a read-only
+ credential would make the scope a formality. An anon key is included
+ because its permissions are chosen per key and may include uploading,
+ deleting, and publishing.
+ enum:
+ - full
+ - read_only
+ x-enum-varnames:
+ - ProjectAccessTokenScopeFull
+ - ProjectAccessTokenScopeReadOnly
+ CreateProjectAccessTokenRequest:
+ type: object
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 100
+ description: |
+ Held by any of the project's tokens you have not revoked, including
+ one that has expired. Creating a duplicate returns 409 with code
+ `access_token_name_exists`; revoking the holder frees the name, so a
+ rotation can keep the name its caller already references.
+ scope:
+ $ref: '#/components/schemas/ProjectAccessTokenScope'
+ expires_at:
+ type: string
+ format: date-time
+ description: Omit for a token that does not expire.
+ required:
+ - name
+ - scope
+ CreatedProjectAccessToken:
+ allOf:
+ - $ref: '#/components/schemas/ProjectAccessToken'
+ - type: object
+ properties:
+ token:
+ type: string
+ description: |
+ The secret. Returned only here, and not recoverable afterwards:
+ the server stores a hash rather than the value. Save it now.
+ required:
+ - token
+ PaginatedProjectAccessTokens:
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProjectAccessToken'
+ page:
+ type: integer
+ limit:
+ type: integer
+ total:
+ type: integer
+ has_more:
+ type: boolean
+ next:
+ type: string
+ required:
+ - data
+ - page
+ - limit
+ - total
+ - has_more
+ ProjectAccessTokenUsage:
+ type: object
+ description: |
+ Zero-filled daily request counts for a single token, oldest first. Every
+ day in the window is present, so a gap reads as zero rather than missing.
+
+ Counts every request the token authenticated, including ones then
+ refused — a read-only token attempting a write, or a token presented on
+ another project's route. That is deliberate: after a leak, the probing
+ is the part you want to see, and a counter that hid it would make a
+ token look idle while it was being tried.
+ properties:
+ token_id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ token_prefix:
+ type: string
+ description: |
+ The token's display prefix, which identifies the credential when its
+ name does not. Revoking frees a name, so a project that rotated
+ `ci-deploy` has two entries here both called `ci-deploy`. Not usable
+ as a credential.
+ days:
+ type: integer
+ description: Number of daily entries returned, always equal to the requested window.
+ daily:
+ type: array
+ items:
+ $ref: '#/components/schemas/ProjectAccessTokenUsageDailyEntry'
+ total_requests:
+ type: integer
+ format: int64
+ required:
+ - token_id
+ - name
+ - token_prefix
+ - days
+ - daily
+ - total_requests
+ ProjectAccessTokenUsageDailyEntry:
+ type: object
+ properties:
+ day:
+ type: string
+ format: date
+ description: UTC day.
+ requests:
+ type: integer
+ format: int64
+ required:
+ - day
+ - requests
Function:
type: object
properties:
@@ -15534,7 +18239,7 @@ components:
type: string
invoke_url:
type: string
- description: Canonical GeoDNS endpoint URL for invoking this function (always HTTPS)
+ description: 'Canonical geo-routed HTTPS endpoint for invoking this function. Use it as-is: it does not share a domain with the API, so a host derived from the API URL will not reach the function. Omitted when the deployment serves no public invocation domain, as in local development, so a client testing for an empty string never matches.'
deployed_regions:
type: array
items:
@@ -15761,12 +18466,18 @@ components:
`succeeded`, `failed`, `timed_out`, `stopped` and `unknown` are
terminal.
- `unknown` means the platform lost track of the execution's outcome: it
- was never seen to finish and is no longer reported, so no result or
- error can be given for it. It is terminal because nothing can settle it
- later, and it is rare — treat it as an outcome to retry under a new
- name rather than a state to wait on. `completed_at` on an `unknown`
- execution is when the platform gave up, not when the work ended.
+ `unknown` means the execution's outcome cannot be established, so no
+ result or error can be given for it. Either it was under way and was
+ never seen to finish, or its start failed with a `500` without the
+ platform establishing whether the execution began — which is why a
+ name whose start returned an error can later read as `unknown` rather
+ than not being found. It is terminal because nothing can settle it
+ later, and it is rare — treat it as an outcome to retry rather than a
+ state to wait on. A retry under the same name picks this execution back
+ up instead of starting a second one, and needs a free concurrency slot
+ because an `unknown` execution has given its own up. `completed_at` on
+ an `unknown` execution is when the platform gave up, not when the work
+ ended.
DurableExecutionError:
type: object
description: Why a failed or timed-out execution ended.
@@ -16967,6 +19678,14 @@ components:
items:
type: string
pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ frontend_shared_variables:
+ type: array
+ uniqueItems: true
+ description: Replace the complete shared frontend-variable list with existing names. Frontends with variable_scope shared receive this list. Omission keeps membership unchanged; an empty list clears it.
+ items:
+ type: string
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ maxLength: 256
variables:
type: array
description: Fully synced when declared - variables absent from this list are deleted.
@@ -17455,11 +20174,31 @@ components:
created or deleted through the manifest. A declared frontend entry
without `custom_domain` deletes an existing custom domain.
properties:
+ variable_scope:
+ type: string
+ enum:
+ - all
+ - shared
+ - scoped
+ description: All preserves access to all project variables. Shared includes the project frontend_shared_variables list. Scoped includes only explicitly declared variables in builds and runtime. Omission preserves the stored selection.
+ variables:
+ type: array
+ uniqueItems: true
+ items:
+ type: string
+ pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$
+ description: Names selected when variable_scope is scoped. Missing declared values reject deployment. Omission preserves the stored list; an empty list clears it.
name:
type: string
minLength: 1
custom_domain:
$ref: '#/components/schemas/ProjectConfigCustomDomain'
+ function_routes:
+ type: array
+ maxItems: 64
+ description: Complete set of same-origin Function path mappings when declared. Omission preserves existing mappings; an empty list deletes all mappings.
+ items:
+ $ref: '#/components/schemas/ProjectConfigFrontendFunctionRoute'
required:
- name
ProjectConfigFunction:
@@ -17486,6 +20225,9 @@ components:
enum:
- all
- scoped
+ x-enum-varnames:
+ - ProjectConfigFunctionVariableScopeAll
+ - ProjectConfigFunctionVariableScopeScoped
description: |
Which project variables this function receives. `all` (the default)
gives it the project variables marked `shared: true`. `scoped` gives it only the variables
@@ -18297,6 +21039,9 @@ components:
type: string
format: uuid
description: Canonical function ID used for invocation routing
+ invoke_url:
+ type: string
+ description: 'Canonical HTTPS endpoint for invoking this function. Use it as-is: it does not share a domain with the API, so a host derived from the API URL will not reach the function. Omitted when the deployment serves no public invocation domain, as in local development; invoke through POST /functions/{functionId}/invoke instead.'
cache_ttl_seconds:
type: integer
minimum: 1
@@ -19077,6 +21822,9 @@ components:
shared:
type: boolean
description: Include this name in the project's shared function variables. Omission preserves existing membership; new variables default to true for legacy clients. Send false explicitly to create a non-shared variable.
+ frontend_shared:
+ type: boolean
+ description: Whether this name is in the project's shared frontend-variable list.
id:
type: string
format: uuid
@@ -19126,6 +21874,12 @@ components:
- value
- created_at
- updated_at
+ OpenAPISpecDocument:
+ type: object
+ additionalProperties: true
+ description: |
+ This OpenAPI document, with every reference resolved. Shared by the JSON
+ and YAML operations, which differ only in serialization.
ProjectGitConnectionSummary:
type: object
properties:
@@ -19219,6 +21973,25 @@ components:
$ref: '#/components/schemas/AuthPageTheme'
layouts:
$ref: '#/components/schemas/ProjectConfigAuthPageLayouts'
+ ProjectConfigFrontendFunctionRoute:
+ type: object
+ additionalProperties: false
+ properties:
+ function:
+ type: string
+ minLength: 1
+ description: Name of an existing standard Function configured for HTTP invocation.
+ path_prefix:
+ type: string
+ minLength: 2
+ maxLength: 512
+ pattern: ^/[^?#\\]*[^/?#\\]$
+ strip_prefix:
+ type: boolean
+ default: false
+ required:
+ - function
+ - path_prefix
DatabaseQueryPerformanceDatabase:
type: object
properties:
diff --git a/src/volcano_sdk/__init__.py b/src/volcano_sdk/__init__.py
index e8e36040..9000bad6 100644
--- a/src/volcano_sdk/__init__.py
+++ b/src/volcano_sdk/__init__.py
@@ -79,6 +79,15 @@
"RealtimeDisconnectContext",
"RealtimeErrorContext",
"RealtimePresenceInfo",
+ "SandboxAccess",
+ "SandboxCommandOptions",
+ "SandboxCommandResult",
+ "SandboxCreateOptions",
+ "SandboxExecOptions",
+ "SandboxExecutionResult",
+ "SandboxPreset",
+ "SandboxSession",
+ "Sandboxes",
"ServerError",
"Session",
"SessionChangedError",
@@ -97,3 +106,15 @@
"VolcanoError",
"database_connection_string",
]
+
+from .sandbox_models import (
+ SandboxAccess,
+ SandboxCommandOptions,
+ SandboxCommandResult,
+ SandboxCreateOptions,
+ SandboxExecOptions,
+ SandboxExecutionResult,
+ SandboxPreset,
+)
+from .sandbox_session import SandboxSession
+from .sandboxes import Sandboxes
diff --git a/src/volcano_sdk/_generated/api/authentication/auth_signin.py b/src/volcano_sdk/_generated/api/authentication/auth_signin.py
index d196c27f..8bf251a6 100644
--- a/src/volcano_sdk/_generated/api/authentication/auth_signin.py
+++ b/src/volcano_sdk/_generated/api/authentication/auth_signin.py
@@ -92,7 +92,9 @@ def sync_detailed(
Set `session_mode` to `cookie` to request HttpOnly refresh-token
storage. Cookie mode is honored only for an exact, credentialed CORS
origin on the same schemeful site as this API. Otherwise the response
- retains the refresh token in its body.
+ retains the refresh token in its body. A frontend on its default
+ Volcano URL is cross-site with this API and so always gets the body
+ token.
Args:
body (AuthSigninBody):
@@ -130,7 +132,9 @@ def sync(
Set `session_mode` to `cookie` to request HttpOnly refresh-token
storage. Cookie mode is honored only for an exact, credentialed CORS
origin on the same schemeful site as this API. Otherwise the response
- retains the refresh token in its body.
+ retains the refresh token in its body. A frontend on its default
+ Volcano URL is cross-site with this API and so always gets the body
+ token.
Args:
body (AuthSigninBody):
@@ -163,7 +167,9 @@ async def asyncio_detailed(
Set `session_mode` to `cookie` to request HttpOnly refresh-token
storage. Cookie mode is honored only for an exact, credentialed CORS
origin on the same schemeful site as this API. Otherwise the response
- retains the refresh token in its body.
+ retains the refresh token in its body. A frontend on its default
+ Volcano URL is cross-site with this API and so always gets the body
+ token.
Args:
body (AuthSigninBody):
@@ -201,7 +207,9 @@ async def asyncio(
Set `session_mode` to `cookie` to request HttpOnly refresh-token
storage. Cookie mode is honored only for an exact, credentialed CORS
origin on the same schemeful site as this API. Otherwise the response
- retains the refresh token in its body.
+ retains the refresh token in its body. A frontend on its default
+ Volcano URL is cross-site with this API and so always gets the body
+ token.
Args:
body (AuthSigninBody):
diff --git a/src/volcano_sdk/_generated/api/durable_functions/delete_durable_function.py b/src/volcano_sdk/_generated/api/durable_functions/delete_durable_function.py
index 82f613d1..94b9646e 100644
--- a/src/volcano_sdk/_generated/api/durable_functions/delete_durable_function.py
+++ b/src/volcano_sdk/_generated/api/durable_functions/delete_durable_function.py
@@ -72,10 +72,14 @@ def sync_detailed(
""" Delete a durable function
Accepted for asynchronous teardown; the work continues after the
- response. The function's executions go with it: history stops being
- readable whatever `retention_days` had left, and the executions still
- running stop counting against the project's concurrency cap. Stop an
- execution first if you need it to end before the function does.
+ response. The function's executions go with it: executions still in
+ flight are stopped, and history stops being readable whatever
+ `retention_days` had left.
+
+ Stopping is asynchronous at the platform, and it does not interrupt a
+ step already running -- that step runs to its next checkpoint. So a
+ delete ends an execution rather than halting it mid-step; stop the
+ execution yourself first if you need to observe it ending.
Args:
id (UUID):
@@ -112,10 +116,14 @@ def sync(
""" Delete a durable function
Accepted for asynchronous teardown; the work continues after the
- response. The function's executions go with it: history stops being
- readable whatever `retention_days` had left, and the executions still
- running stop counting against the project's concurrency cap. Stop an
- execution first if you need it to end before the function does.
+ response. The function's executions go with it: executions still in
+ flight are stopped, and history stops being readable whatever
+ `retention_days` had left.
+
+ Stopping is asynchronous at the platform, and it does not interrupt a
+ step already running -- that step runs to its next checkpoint. So a
+ delete ends an execution rather than halting it mid-step; stop the
+ execution yourself first if you need to observe it ending.
Args:
id (UUID):
@@ -147,10 +155,14 @@ async def asyncio_detailed(
""" Delete a durable function
Accepted for asynchronous teardown; the work continues after the
- response. The function's executions go with it: history stops being
- readable whatever `retention_days` had left, and the executions still
- running stop counting against the project's concurrency cap. Stop an
- execution first if you need it to end before the function does.
+ response. The function's executions go with it: executions still in
+ flight are stopped, and history stops being readable whatever
+ `retention_days` had left.
+
+ Stopping is asynchronous at the platform, and it does not interrupt a
+ step already running -- that step runs to its next checkpoint. So a
+ delete ends an execution rather than halting it mid-step; stop the
+ execution yourself first if you need to observe it ending.
Args:
id (UUID):
@@ -187,10 +199,14 @@ async def asyncio(
""" Delete a durable function
Accepted for asynchronous teardown; the work continues after the
- response. The function's executions go with it: history stops being
- readable whatever `retention_days` had left, and the executions still
- running stop counting against the project's concurrency cap. Stop an
- execution first if you need it to end before the function does.
+ response. The function's executions go with it: executions still in
+ flight are stopped, and history stops being readable whatever
+ `retention_days` had left.
+
+ Stopping is asynchronous at the platform, and it does not interrupt a
+ step already running -- that step runs to its next checkpoint. So a
+ delete ends an execution rather than halting it mid-step; stop the
+ execution yourself first if you need to observe it ending.
Args:
id (UUID):
diff --git a/src/volcano_sdk/_generated/api/durable_functions/list_durable_executions.py b/src/volcano_sdk/_generated/api/durable_functions/list_durable_executions.py
index 617f9b91..4a02b614 100644
--- a/src/volcano_sdk/_generated/api/durable_functions/list_durable_executions.py
+++ b/src/volcano_sdk/_generated/api/durable_functions/list_durable_executions.py
@@ -122,12 +122,18 @@ def sync_detailed(
`succeeded`, `failed`, `timed_out`, `stopped` and `unknown` are
terminal.
- `unknown` means the platform lost track of the execution's outcome: it
- was never seen to finish and is no longer reported, so no result or
- error can be given for it. It is terminal because nothing can settle it
- later, and it is rare — treat it as an outcome to retry under a new
- name rather than a state to wait on. `completed_at` on an `unknown`
- execution is when the platform gave up, not when the work ended.
+ `unknown` means the execution's outcome cannot be established, so no
+ result or error can be given for it. Either it was under way and was
+ never seen to finish, or its start failed with a `500` without the
+ platform establishing whether the execution began — which is why a
+ name whose start returned an error can later read as `unknown` rather
+ than not being found. It is terminal because nothing can settle it
+ later, and it is rare — treat it as an outcome to retry rather than a
+ state to wait on. A retry under the same name picks this execution back
+ up instead of starting a second one, and needs a free concurrency slot
+ because an `unknown` execution has given its own up. `completed_at` on
+ an `unknown` execution is when the platform gave up, not when the work
+ ended.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -180,12 +186,18 @@ def sync(
`succeeded`, `failed`, `timed_out`, `stopped` and `unknown` are
terminal.
- `unknown` means the platform lost track of the execution's outcome: it
- was never seen to finish and is no longer reported, so no result or
- error can be given for it. It is terminal because nothing can settle it
- later, and it is rare — treat it as an outcome to retry under a new
- name rather than a state to wait on. `completed_at` on an `unknown`
- execution is when the platform gave up, not when the work ended.
+ `unknown` means the execution's outcome cannot be established, so no
+ result or error can be given for it. Either it was under way and was
+ never seen to finish, or its start failed with a `500` without the
+ platform establishing whether the execution began — which is why a
+ name whose start returned an error can later read as `unknown` rather
+ than not being found. It is terminal because nothing can settle it
+ later, and it is rare — treat it as an outcome to retry rather than a
+ state to wait on. A retry under the same name picks this execution back
+ up instead of starting a second one, and needs a free concurrency slot
+ because an `unknown` execution has given its own up. `completed_at` on
+ an `unknown` execution is when the platform gave up, not when the work
+ ended.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -233,12 +245,18 @@ async def asyncio_detailed(
`succeeded`, `failed`, `timed_out`, `stopped` and `unknown` are
terminal.
- `unknown` means the platform lost track of the execution's outcome: it
- was never seen to finish and is no longer reported, so no result or
- error can be given for it. It is terminal because nothing can settle it
- later, and it is rare — treat it as an outcome to retry under a new
- name rather than a state to wait on. `completed_at` on an `unknown`
- execution is when the platform gave up, not when the work ended.
+ `unknown` means the execution's outcome cannot be established, so no
+ result or error can be given for it. Either it was under way and was
+ never seen to finish, or its start failed with a `500` without the
+ platform establishing whether the execution began — which is why a
+ name whose start returned an error can later read as `unknown` rather
+ than not being found. It is terminal because nothing can settle it
+ later, and it is rare — treat it as an outcome to retry rather than a
+ state to wait on. A retry under the same name picks this execution back
+ up instead of starting a second one, and needs a free concurrency slot
+ because an `unknown` execution has given its own up. `completed_at` on
+ an `unknown` execution is when the platform gave up, not when the work
+ ended.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
@@ -291,12 +309,18 @@ async def asyncio(
`succeeded`, `failed`, `timed_out`, `stopped` and `unknown` are
terminal.
- `unknown` means the platform lost track of the execution's outcome: it
- was never seen to finish and is no longer reported, so no result or
- error can be given for it. It is terminal because nothing can settle it
- later, and it is rare — treat it as an outcome to retry under a new
- name rather than a state to wait on. `completed_at` on an `unknown`
- execution is when the platform gave up, not when the work ended.
+ `unknown` means the execution's outcome cannot be established, so no
+ result or error can be given for it. Either it was under way and was
+ never seen to finish, or its start failed with a `500` without the
+ platform establishing whether the execution began — which is why a
+ name whose start returned an error can later read as `unknown` rather
+ than not being found. It is terminal because nothing can settle it
+ later, and it is rare — treat it as an outcome to retry rather than a
+ state to wait on. A retry under the same name picks this execution back
+ up instead of starting a second one, and needs a free concurrency slot
+ because an `unknown` execution has given its own up. `completed_at` on
+ an `unknown` execution is when the platform gave up, not when the work
+ ended.
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
diff --git a/src/volcano_sdk/_generated/api/frontends/create_frontend.py b/src/volcano_sdk/_generated/api/frontends/create_frontend.py
index ec8e6bdd..9923c1e1 100644
--- a/src/volcano_sdk/_generated/api/frontends/create_frontend.py
+++ b/src/volcano_sdk/_generated/api/frontends/create_frontend.py
@@ -152,8 +152,8 @@ def sync_detailed(
22.x or 24.x. The Node.js runtime is inferred from
`package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x.
The selected Node.js family must also satisfy the installed Next.js package's
- `engines.node` constraint. Volcano tests Next 15.5.25 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
- 16.3.5 (`>=20.9.0`).
+ `engines.node` constraint. Volcano tests Next 15.5.26 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
+ 16.3.6 (`>=20.9.0`).
Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
does not apply its own source archive size limit. After the final container images are
built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
@@ -216,8 +216,8 @@ def sync(
22.x or 24.x. The Node.js runtime is inferred from
`package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x.
The selected Node.js family must also satisfy the installed Next.js package's
- `engines.node` constraint. Volcano tests Next 15.5.25 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
- 16.3.5 (`>=20.9.0`).
+ `engines.node` constraint. Volcano tests Next 15.5.26 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
+ 16.3.6 (`>=20.9.0`).
Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
does not apply its own source archive size limit. After the final container images are
built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
@@ -275,8 +275,8 @@ async def asyncio_detailed(
22.x or 24.x. The Node.js runtime is inferred from
`package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x.
The selected Node.js family must also satisfy the installed Next.js package's
- `engines.node` constraint. Volcano tests Next 15.5.25 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
- 16.3.5 (`>=20.9.0`).
+ `engines.node` constraint. Volcano tests Next 15.5.26 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
+ 16.3.6 (`>=20.9.0`).
Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
does not apply its own source archive size limit. After the final container images are
built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
@@ -339,8 +339,8 @@ async def asyncio(
22.x or 24.x. The Node.js runtime is inferred from
`package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x.
The selected Node.js family must also satisfy the installed Next.js package's
- `engines.node` constraint. Volcano tests Next 15.5.25 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
- 16.3.5 (`>=20.9.0`).
+ `engines.node` constraint. Volcano tests Next 15.5.26 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next
+ 16.3.6 (`>=20.9.0`).
Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI
does not apply its own source archive size limit. After the final container images are
built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing.
diff --git a/src/volcano_sdk/_generated/api/frontends/create_frontend_function_route.py b/src/volcano_sdk/_generated/api/frontends/create_frontend_function_route.py
new file mode 100644
index 00000000..42563f78
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/frontends/create_frontend_function_route.py
@@ -0,0 +1,264 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.create_frontend_function_route_request import CreateFrontendFunctionRouteRequest
+from ...models.error import Error
+from ...models.frontend_function_route import FrontendFunctionRoute
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/projects/{id}/frontends/{frontend_id}/function-routes".format(id=quote(str(id), safe=""),frontend_id=quote(str(frontend_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | FrontendFunctionRoute | None:
+ if response.status_code == 201:
+ response_201 = FrontendFunctionRoute.from_dict(response.json())
+
+
+
+ return response_201
+
+ if response.status_code == 400:
+ response_400 = Error.from_dict(response.json())
+
+
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = Error.from_dict(response.json())
+
+
+
+ return response_409
+
+ if response.status_code == 500:
+ response_500 = Error.from_dict(response.json())
+
+
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | FrontendFunctionRoute]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Response[Error | FrontendFunctionRoute]:
+ """ Route a Frontend path to an HTTP Function
+
+ The Frontend and Function must belong to this Project. The Function may be private but must use HTTP
+ invocation mode. The route applies to every hostname that resolves to the Frontend, including
+ generated, custom-domain, preview, and local hostnames.
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | FrontendFunctionRoute]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Error | FrontendFunctionRoute | None:
+ """ Route a Frontend path to an HTTP Function
+
+ The Frontend and Function must belong to this Project. The Function may be private but must use HTTP
+ invocation mode. The route applies to every hostname that resolves to the Frontend, including
+ generated, custom-domain, preview, and local hostnames.
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | FrontendFunctionRoute
+ """
+
+
+ return sync_detailed(
+ id=id,
+frontend_id=frontend_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Response[Error | FrontendFunctionRoute]:
+ """ Route a Frontend path to an HTTP Function
+
+ The Frontend and Function must belong to this Project. The Function may be private but must use HTTP
+ invocation mode. The route applies to every hostname that resolves to the Frontend, including
+ generated, custom-domain, preview, and local hostnames.
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | FrontendFunctionRoute]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Error | FrontendFunctionRoute | None:
+ """ Route a Frontend path to an HTTP Function
+
+ The Frontend and Function must belong to this Project. The Function may be private but must use HTTP
+ invocation mode. The route applies to every hostname that resolves to the Frontend, including
+ generated, custom-domain, preview, and local hostnames.
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | FrontendFunctionRoute
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+frontend_id=frontend_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/frontends/delete_frontend_function_route.py b/src/volcano_sdk/_generated/api/frontends/delete_frontend_function_route.py
new file mode 100644
index 00000000..6dded1c7
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/frontends/delete_frontend_function_route.py
@@ -0,0 +1,223 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "delete",
+ "url": "/projects/{id}/frontends/{frontend_id}/function-routes/{route_id}".format(id=quote(str(id), safe=""),frontend_id=quote(str(frontend_id), safe=""),route_id=quote(str(route_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None:
+ if response.status_code == 204:
+ response_204 = cast(Any, None)
+ return response_204
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if response.status_code == 500:
+ response_500 = Error.from_dict(response.json())
+
+
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Delete a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Delete a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Delete a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Delete a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/frontends/list_frontend_function_routes.py b/src/volcano_sdk/_generated/api/frontends/list_frontend_function_routes.py
new file mode 100644
index 00000000..a3ccf7a3
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/frontends/list_frontend_function_routes.py
@@ -0,0 +1,207 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.frontend_function_route_list import FrontendFunctionRouteList
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ frontend_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/frontends/{frontend_id}/function-routes".format(id=quote(str(id), safe=""),frontend_id=quote(str(frontend_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | FrontendFunctionRouteList | None:
+ if response.status_code == 200:
+ response_200 = FrontendFunctionRouteList.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 500:
+ response_500 = Error.from_dict(response.json())
+
+
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | FrontendFunctionRouteList]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | FrontendFunctionRouteList]:
+ """ List a Frontend's Function routes
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | FrontendFunctionRouteList]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | FrontendFunctionRouteList | None:
+ """ List a Frontend's Function routes
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | FrontendFunctionRouteList
+ """
+
+
+ return sync_detailed(
+ id=id,
+frontend_id=frontend_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | FrontendFunctionRouteList]:
+ """ List a Frontend's Function routes
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | FrontendFunctionRouteList]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | FrontendFunctionRouteList | None:
+ """ List a Frontend's Function routes
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | FrontendFunctionRouteList
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+frontend_id=frontend_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/frontends/update_frontend_function_route.py b/src/volcano_sdk/_generated/api/frontends/update_frontend_function_route.py
new file mode 100644
index 00000000..b757fea0
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/frontends/update_frontend_function_route.py
@@ -0,0 +1,261 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.create_frontend_function_route_request import CreateFrontendFunctionRouteRequest
+from ...models.error import Error
+from ...models.frontend_function_route import FrontendFunctionRoute
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "put",
+ "url": "/projects/{id}/frontends/{frontend_id}/function-routes/{route_id}".format(id=quote(str(id), safe=""),frontend_id=quote(str(frontend_id), safe=""),route_id=quote(str(route_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | FrontendFunctionRoute | None:
+ if response.status_code == 200:
+ response_200 = FrontendFunctionRoute.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = Error.from_dict(response.json())
+
+
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = Error.from_dict(response.json())
+
+
+
+ return response_409
+
+ if response.status_code == 500:
+ response_500 = Error.from_dict(response.json())
+
+
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | FrontendFunctionRoute]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Response[Error | FrontendFunctionRoute]:
+ """ Replace a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | FrontendFunctionRoute]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Error | FrontendFunctionRoute | None:
+ """ Replace a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | FrontendFunctionRoute
+ """
+
+
+ return sync_detailed(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Response[Error | FrontendFunctionRoute]:
+ """ Replace a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | FrontendFunctionRoute]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ frontend_id: UUID | str,
+ route_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateFrontendFunctionRouteRequest,
+
+) -> Error | FrontendFunctionRoute | None:
+ """ Replace a Frontend Function route
+
+ Args:
+ id (UUID):
+ frontend_id (UUID):
+ route_id (UUID):
+ body (CreateFrontendFunctionRouteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | FrontendFunctionRoute
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+frontend_id=frontend_id,
+route_id=route_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/functions/create_function.py b/src/volcano_sdk/_generated/api/functions/create_function.py
index 1e2509af..b87c3fb9 100644
--- a/src/volcano_sdk/_generated/api/functions/create_function.py
+++ b/src/volcano_sdk/_generated/api/functions/create_function.py
@@ -79,6 +79,13 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return response_409
+ if response.status_code == 429:
+ response_429 = Error.from_dict(response.json())
+
+
+
+ return response_429
+
if response.status_code == 500:
response_500 = Error.from_dict(response.json())
@@ -86,6 +93,13 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return response_500
+ if response.status_code == 503:
+ response_503 = Error.from_dict(response.json())
+
+
+
+ return response_503
+
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
diff --git a/src/volcano_sdk/_generated/api/functions/create_functions_batch.py b/src/volcano_sdk/_generated/api/functions/create_functions_batch.py
index 3776222a..ff466399 100644
--- a/src/volcano_sdk/_generated/api/functions/create_functions_batch.py
+++ b/src/volcano_sdk/_generated/api/functions/create_functions_batch.py
@@ -79,6 +79,13 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return response_409
+ if response.status_code == 429:
+ response_429 = Error.from_dict(response.json())
+
+
+
+ return response_429
+
if response.status_code == 503:
response_503 = Error.from_dict(response.json())
diff --git a/src/volcano_sdk/_generated/api/functions/invoke_function.py b/src/volcano_sdk/_generated/api/functions/invoke_function.py
index 06c583dc..9211a681 100644
--- a/src/volcano_sdk/_generated/api/functions/invoke_function.py
+++ b/src/volcano_sdk/_generated/api/functions/invoke_function.py
@@ -148,7 +148,8 @@ def sync_detailed(
- This operation is the authenticated direct RPC endpoint and always uses the
POST `{payload: ...}` contract, including for functions whose DNS ingress is
configured in HTTP mode.
- - The geo-routed DNS ingress is `https://{functionId}.functions./`.
+ - The geo-routed DNS ingress is the function's `invoke_url`. It is on a
+ different domain from this API, so it cannot be derived from the API host.
- RPC-mode DNS ingress accepts POST at `/`. HTTP-mode DNS ingress accepts GET,
HEAD, POST, PUT, PATCH, and DELETE at `/` and nested paths.
- Direct and RPC-mode CORS preflight advertises `POST, OPTIONS`. HTTP-mode DNS
@@ -225,7 +226,8 @@ def sync(
- This operation is the authenticated direct RPC endpoint and always uses the
POST `{payload: ...}` contract, including for functions whose DNS ingress is
configured in HTTP mode.
- - The geo-routed DNS ingress is `https://{functionId}.functions./`.
+ - The geo-routed DNS ingress is the function's `invoke_url`. It is on a
+ different domain from this API, so it cannot be derived from the API host.
- RPC-mode DNS ingress accepts POST at `/`. HTTP-mode DNS ingress accepts GET,
HEAD, POST, PUT, PATCH, and DELETE at `/` and nested paths.
- Direct and RPC-mode CORS preflight advertises `POST, OPTIONS`. HTTP-mode DNS
@@ -297,7 +299,8 @@ async def asyncio_detailed(
- This operation is the authenticated direct RPC endpoint and always uses the
POST `{payload: ...}` contract, including for functions whose DNS ingress is
configured in HTTP mode.
- - The geo-routed DNS ingress is `https://{functionId}.functions./`.
+ - The geo-routed DNS ingress is the function's `invoke_url`. It is on a
+ different domain from this API, so it cannot be derived from the API host.
- RPC-mode DNS ingress accepts POST at `/`. HTTP-mode DNS ingress accepts GET,
HEAD, POST, PUT, PATCH, and DELETE at `/` and nested paths.
- Direct and RPC-mode CORS preflight advertises `POST, OPTIONS`. HTTP-mode DNS
@@ -374,7 +377,8 @@ async def asyncio(
- This operation is the authenticated direct RPC endpoint and always uses the
POST `{payload: ...}` contract, including for functions whose DNS ingress is
configured in HTTP mode.
- - The geo-routed DNS ingress is `https://{functionId}.functions./`.
+ - The geo-routed DNS ingress is the function's `invoke_url`. It is on a
+ different domain from this API, so it cannot be derived from the API host.
- RPC-mode DNS ingress accepts POST at `/`. HTTP-mode DNS ingress accepts GET,
HEAD, POST, PUT, PATCH, and DELETE at `/` and nested paths.
- Direct and RPC-mode CORS preflight advertises `POST, OPTIONS`. HTTP-mode DNS
diff --git a/src/volcano_sdk/_generated/api/functions/resolve_function_for_invocation.py b/src/volcano_sdk/_generated/api/functions/resolve_function_for_invocation.py
index 74f60708..abb6318c 100644
--- a/src/volcano_sdk/_generated/api/functions/resolve_function_for_invocation.py
+++ b/src/volcano_sdk/_generated/api/functions/resolve_function_for_invocation.py
@@ -101,9 +101,13 @@ def sync_detailed(
) -> Response[Error | ResolveFunctionResponse]:
""" Resolve function name for invocation
- Resolves a DNS-safe function name to its function ID within the caller's project.
+ Resolves a DNS-safe function name to its function ID and invocation URL within the caller's project.
SDKs use this endpoint internally to invoke by function name while routing by function ID.
+ Invoke the returned `invoke_url` as-is. It does not share a domain with the API, so a host
+ built from the API URL will not reach the function. When the deployment serves no public
+ invocation domain, as in local development, `invoke_url` is omitted and callers invoke
+ through `POST /functions/{functionId}/invoke`.
**With Service Key**:
- Allowed
@@ -146,9 +150,13 @@ def sync(
) -> Error | ResolveFunctionResponse | None:
""" Resolve function name for invocation
- Resolves a DNS-safe function name to its function ID within the caller's project.
+ Resolves a DNS-safe function name to its function ID and invocation URL within the caller's project.
SDKs use this endpoint internally to invoke by function name while routing by function ID.
+ Invoke the returned `invoke_url` as-is. It does not share a domain with the API, so a host
+ built from the API URL will not reach the function. When the deployment serves no public
+ invocation domain, as in local development, `invoke_url` is omitted and callers invoke
+ through `POST /functions/{functionId}/invoke`.
**With Service Key**:
- Allowed
@@ -186,9 +194,13 @@ async def asyncio_detailed(
) -> Response[Error | ResolveFunctionResponse]:
""" Resolve function name for invocation
- Resolves a DNS-safe function name to its function ID within the caller's project.
+ Resolves a DNS-safe function name to its function ID and invocation URL within the caller's project.
SDKs use this endpoint internally to invoke by function name while routing by function ID.
+ Invoke the returned `invoke_url` as-is. It does not share a domain with the API, so a host
+ built from the API URL will not reach the function. When the deployment serves no public
+ invocation domain, as in local development, `invoke_url` is omitted and callers invoke
+ through `POST /functions/{functionId}/invoke`.
**With Service Key**:
- Allowed
@@ -231,9 +243,13 @@ async def asyncio(
) -> Error | ResolveFunctionResponse | None:
""" Resolve function name for invocation
- Resolves a DNS-safe function name to its function ID within the caller's project.
+ Resolves a DNS-safe function name to its function ID and invocation URL within the caller's project.
SDKs use this endpoint internally to invoke by function name while routing by function ID.
+ Invoke the returned `invoke_url` as-is. It does not share a domain with the API, so a host
+ built from the API URL will not reach the function. When the deployment serves no public
+ invocation domain, as in local development, `invoke_url` is omitted and callers invoke
+ through `POST /functions/{functionId}/invoke`.
**With Service Key**:
- Allowed
diff --git a/src/volcano_sdk/_generated/api/functions/update_function.py b/src/volcano_sdk/_generated/api/functions/update_function.py
index 0e5c74c3..4942f0d9 100644
--- a/src/volcano_sdk/_generated/api/functions/update_function.py
+++ b/src/volcano_sdk/_generated/api/functions/update_function.py
@@ -66,6 +66,13 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return response_404
+ if response.status_code == 409:
+ response_409 = Error.from_dict(response.json())
+
+
+
+ return response_409
+
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/__init__.py b/src/volcano_sdk/_generated/api/project_access_tokens/__init__.py
new file mode 100644
index 00000000..c9921b5f
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/__init__.py
@@ -0,0 +1 @@
+""" Contains endpoint functions for accessing the API """
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/create_project_access_token.py b/src/volcano_sdk/_generated/api/project_access_tokens/create_project_access_token.py
new file mode 100644
index 00000000..af580cde
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/create_project_access_token.py
@@ -0,0 +1,284 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.create_project_access_token_request import CreateProjectAccessTokenRequest
+from ...models.created_project_access_token import CreatedProjectAccessToken
+from ...models.error import Error
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ body: CreateProjectAccessTokenRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/projects/{id}/access-tokens".format(id=quote(str(id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> CreatedProjectAccessToken | Error | None:
+ if response.status_code == 201:
+ response_201 = CreatedProjectAccessToken.from_dict(response.json())
+
+
+
+ return response_201
+
+ if response.status_code == 400:
+ response_400 = Error.from_dict(response.json())
+
+
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = Error.from_dict(response.json())
+
+
+
+ return response_409
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[CreatedProjectAccessToken | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateProjectAccessTokenRequest,
+
+) -> Response[CreatedProjectAccessToken | Error]:
+ """ Create a project access token
+
+ Creates a project access token and returns its secret.
+
+ The secret is in this response and nowhere else. Only its hash is
+ stored, so it cannot be retrieved, displayed, or recovered later — save
+ it when you create it.
+
+ The name must be unique within the project, so a retry cannot mint a
+ second credential. It cannot recover the first one either. A retry that
+ returns `409` with code `access_token_name_exists` means the original
+ create committed and its secret is unrecoverable: list the project's
+ tokens, revoke the one holding that name, and create it again.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ body (CreateProjectAccessTokenRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[CreatedProjectAccessToken | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateProjectAccessTokenRequest,
+
+) -> CreatedProjectAccessToken | Error | None:
+ """ Create a project access token
+
+ Creates a project access token and returns its secret.
+
+ The secret is in this response and nowhere else. Only its hash is
+ stored, so it cannot be retrieved, displayed, or recovered later — save
+ it when you create it.
+
+ The name must be unique within the project, so a retry cannot mint a
+ second credential. It cannot recover the first one either. A retry that
+ returns `409` with code `access_token_name_exists` means the original
+ create committed and its secret is unrecoverable: list the project's
+ tokens, revoke the one holding that name, and create it again.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ body (CreateProjectAccessTokenRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ CreatedProjectAccessToken | Error
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateProjectAccessTokenRequest,
+
+) -> Response[CreatedProjectAccessToken | Error]:
+ """ Create a project access token
+
+ Creates a project access token and returns its secret.
+
+ The secret is in this response and nowhere else. Only its hash is
+ stored, so it cannot be retrieved, displayed, or recovered later — save
+ it when you create it.
+
+ The name must be unique within the project, so a retry cannot mint a
+ second credential. It cannot recover the first one either. A retry that
+ returns `409` with code `access_token_name_exists` means the original
+ create committed and its secret is unrecoverable: list the project's
+ tokens, revoke the one holding that name, and create it again.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ body (CreateProjectAccessTokenRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[CreatedProjectAccessToken | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateProjectAccessTokenRequest,
+
+) -> CreatedProjectAccessToken | Error | None:
+ """ Create a project access token
+
+ Creates a project access token and returns its secret.
+
+ The secret is in this response and nowhere else. Only its hash is
+ stored, so it cannot be retrieved, displayed, or recovered later — save
+ it when you create it.
+
+ The name must be unique within the project, so a retry cannot mint a
+ second credential. It cannot recover the first one either. A retry that
+ returns `409` with code `access_token_name_exists` means the original
+ create committed and its secret is unrecoverable: list the project's
+ tokens, revoke the one holding that name, and create it again.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ body (CreateProjectAccessTokenRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ CreatedProjectAccessToken | Error
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/get_project_access_token.py b/src/volcano_sdk/_generated/api/project_access_tokens/get_project_access_token.py
new file mode 100644
index 00000000..48683f0c
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/get_project_access_token.py
@@ -0,0 +1,227 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.project_access_token import ProjectAccessToken
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ token_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/access-tokens/{token_id}".format(id=quote(str(id), safe=""),token_id=quote(str(token_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | ProjectAccessToken | None:
+ if response.status_code == 200:
+ response_200 = ProjectAccessToken.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | ProjectAccessToken]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | ProjectAccessToken]:
+ """ Get a project access token
+
+ Returns one token's metadata. Never its secret, which is not stored in a
+ recoverable form.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | ProjectAccessToken]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+token_id=token_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | ProjectAccessToken | None:
+ """ Get a project access token
+
+ Returns one token's metadata. Never its secret, which is not stored in a
+ recoverable form.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | ProjectAccessToken
+ """
+
+
+ return sync_detailed(
+ id=id,
+token_id=token_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | ProjectAccessToken]:
+ """ Get a project access token
+
+ Returns one token's metadata. Never its secret, which is not stored in a
+ recoverable form.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | ProjectAccessToken]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+token_id=token_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | ProjectAccessToken | None:
+ """ Get a project access token
+
+ Returns one token's metadata. Never its secret, which is not stored in a
+ recoverable form.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | ProjectAccessToken
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+token_id=token_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/get_project_access_token_usage.py b/src/volcano_sdk/_generated/api/project_access_tokens/get_project_access_token_usage.py
new file mode 100644
index 00000000..fe8a62c1
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/get_project_access_token_usage.py
@@ -0,0 +1,276 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.project_access_token_usage import ProjectAccessTokenUsage
+from ...types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ days: int | Unset = 30,
+
+) -> dict[str, Any]:
+
+
+
+
+ params: dict[str, Any] = {}
+
+ params["days"] = days
+
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/access-tokens/{token_id}/usage".format(id=quote(str(id), safe=""),token_id=quote(str(token_id), safe=""),),
+ "params": params,
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | ProjectAccessTokenUsage | None:
+ if response.status_code == 200:
+ response_200 = ProjectAccessTokenUsage.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = Error.from_dict(response.json())
+
+
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | ProjectAccessTokenUsage]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Response[Error | ProjectAccessTokenUsage]:
+ """ Per-day request counts for one access token
+
+ Returns a zero-filled daily series of request counts for a single token,
+ oldest first, so the response always has exactly `days` entries.
+
+ `days` defaults to 30 and is capped at 60, matching how long per-day
+ counts are retained.
+
+ A project access token may read only its own usage; asking for another
+ token's returns `403`. A platform token may read any token in the
+ project.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | ProjectAccessTokenUsage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+token_id=token_id,
+days=days,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Error | ProjectAccessTokenUsage | None:
+ """ Per-day request counts for one access token
+
+ Returns a zero-filled daily series of request counts for a single token,
+ oldest first, so the response always has exactly `days` entries.
+
+ `days` defaults to 30 and is capped at 60, matching how long per-day
+ counts are retained.
+
+ A project access token may read only its own usage; asking for another
+ token's returns `403`. A platform token may read any token in the
+ project.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | ProjectAccessTokenUsage
+ """
+
+
+ return sync_detailed(
+ id=id,
+token_id=token_id,
+client=client,
+days=days,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Response[Error | ProjectAccessTokenUsage]:
+ """ Per-day request counts for one access token
+
+ Returns a zero-filled daily series of request counts for a single token,
+ oldest first, so the response always has exactly `days` entries.
+
+ `days` defaults to 30 and is capped at 60, matching how long per-day
+ counts are retained.
+
+ A project access token may read only its own usage; asking for another
+ token's returns `403`. A platform token may read any token in the
+ project.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | ProjectAccessTokenUsage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+token_id=token_id,
+days=days,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Error | ProjectAccessTokenUsage | None:
+ """ Per-day request counts for one access token
+
+ Returns a zero-filled daily series of request counts for a single token,
+ oldest first, so the response always has exactly `days` entries.
+
+ `days` defaults to 30 and is capped at 60, matching how long per-day
+ counts are retained.
+
+ A project access token may read only its own usage; asking for another
+ token's returns `403`. A platform token may read any token in the
+ project.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | ProjectAccessTokenUsage
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+token_id=token_id,
+client=client,
+days=days,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/list_project_access_tokens.py b/src/volcano_sdk/_generated/api/project_access_tokens/list_project_access_tokens.py
new file mode 100644
index 00000000..c84f3230
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/list_project_access_tokens.py
@@ -0,0 +1,305 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.paginated_project_access_tokens import PaginatedProjectAccessTokens
+from ...types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ page: int | Unset = UNSET,
+ limit: int | Unset = 10,
+ search: str | Unset = UNSET,
+ include_revoked: bool | Unset = False,
+
+) -> dict[str, Any]:
+
+
+
+
+ params: dict[str, Any] = {}
+
+ params["page"] = page
+
+ params["limit"] = limit
+
+ params["search"] = search
+
+ params["include_revoked"] = include_revoked
+
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/access-tokens".format(id=quote(str(id), safe=""),),
+ "params": params,
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | PaginatedProjectAccessTokens | None:
+ if response.status_code == 200:
+ response_200 = PaginatedProjectAccessTokens.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | PaginatedProjectAccessTokens]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ page: int | Unset = UNSET,
+ limit: int | Unset = 10,
+ search: str | Unset = UNSET,
+ include_revoked: bool | Unset = False,
+
+) -> Response[Error | PaginatedProjectAccessTokens]:
+ """ List a project's access tokens
+
+ Lists the project's access tokens, newest first. Secrets are never
+ returned: only a hash is stored, so a token's value exists solely in the
+ response to the create call.
+
+ Only tokens that can still authenticate are returned by default, so
+ revoked and expired ones are hidden. Pass `include_revoked=true` to see
+ them, which is how you find out what a key did before it stopped working.
+
+ Requires a platform token. A project access token cannot manage project
+ access tokens, so a leaked credential cannot enumerate or replace itself.
+
+ Args:
+ id (UUID):
+ page (int | Unset):
+ limit (int | Unset): Default: 10.
+ search (str | Unset):
+ include_revoked (bool | Unset): Default: False.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | PaginatedProjectAccessTokens]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+page=page,
+limit=limit,
+search=search,
+include_revoked=include_revoked,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ page: int | Unset = UNSET,
+ limit: int | Unset = 10,
+ search: str | Unset = UNSET,
+ include_revoked: bool | Unset = False,
+
+) -> Error | PaginatedProjectAccessTokens | None:
+ """ List a project's access tokens
+
+ Lists the project's access tokens, newest first. Secrets are never
+ returned: only a hash is stored, so a token's value exists solely in the
+ response to the create call.
+
+ Only tokens that can still authenticate are returned by default, so
+ revoked and expired ones are hidden. Pass `include_revoked=true` to see
+ them, which is how you find out what a key did before it stopped working.
+
+ Requires a platform token. A project access token cannot manage project
+ access tokens, so a leaked credential cannot enumerate or replace itself.
+
+ Args:
+ id (UUID):
+ page (int | Unset):
+ limit (int | Unset): Default: 10.
+ search (str | Unset):
+ include_revoked (bool | Unset): Default: False.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | PaginatedProjectAccessTokens
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+page=page,
+limit=limit,
+search=search,
+include_revoked=include_revoked,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ page: int | Unset = UNSET,
+ limit: int | Unset = 10,
+ search: str | Unset = UNSET,
+ include_revoked: bool | Unset = False,
+
+) -> Response[Error | PaginatedProjectAccessTokens]:
+ """ List a project's access tokens
+
+ Lists the project's access tokens, newest first. Secrets are never
+ returned: only a hash is stored, so a token's value exists solely in the
+ response to the create call.
+
+ Only tokens that can still authenticate are returned by default, so
+ revoked and expired ones are hidden. Pass `include_revoked=true` to see
+ them, which is how you find out what a key did before it stopped working.
+
+ Requires a platform token. A project access token cannot manage project
+ access tokens, so a leaked credential cannot enumerate or replace itself.
+
+ Args:
+ id (UUID):
+ page (int | Unset):
+ limit (int | Unset): Default: 10.
+ search (str | Unset):
+ include_revoked (bool | Unset): Default: False.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | PaginatedProjectAccessTokens]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+page=page,
+limit=limit,
+search=search,
+include_revoked=include_revoked,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ page: int | Unset = UNSET,
+ limit: int | Unset = 10,
+ search: str | Unset = UNSET,
+ include_revoked: bool | Unset = False,
+
+) -> Error | PaginatedProjectAccessTokens | None:
+ """ List a project's access tokens
+
+ Lists the project's access tokens, newest first. Secrets are never
+ returned: only a hash is stored, so a token's value exists solely in the
+ response to the create call.
+
+ Only tokens that can still authenticate are returned by default, so
+ revoked and expired ones are hidden. Pass `include_revoked=true` to see
+ them, which is how you find out what a key did before it stopped working.
+
+ Requires a platform token. A project access token cannot manage project
+ access tokens, so a leaked credential cannot enumerate or replace itself.
+
+ Args:
+ id (UUID):
+ page (int | Unset):
+ limit (int | Unset): Default: 10.
+ search (str | Unset):
+ include_revoked (bool | Unset): Default: False.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | PaginatedProjectAccessTokens
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+page=page,
+limit=limit,
+search=search,
+include_revoked=include_revoked,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/list_project_access_tokens_usage.py b/src/volcano_sdk/_generated/api/project_access_tokens/list_project_access_tokens_usage.py
new file mode 100644
index 00000000..821aeb23
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/list_project_access_tokens_usage.py
@@ -0,0 +1,284 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.project_access_token_usage import ProjectAccessTokenUsage
+from ...types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ days: int | Unset = 30,
+
+) -> dict[str, Any]:
+
+
+
+
+ params: dict[str, Any] = {}
+
+ params["days"] = days
+
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/access-tokens/usage".format(id=quote(str(id), safe=""),),
+ "params": params,
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | list[ProjectAccessTokenUsage] | None:
+ if response.status_code == 200:
+ response_200 = []
+ _response_200 = response.json()
+ for response_200_item_data in (_response_200):
+ response_200_item = ProjectAccessTokenUsage.from_dict(response_200_item_data)
+
+
+
+ response_200.append(response_200_item)
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = Error.from_dict(response.json())
+
+
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | list[ProjectAccessTokenUsage]]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Response[Error | list[ProjectAccessTokenUsage]]:
+ """ Per-day request counts for every access token in a project
+
+ Returns a zero-filled daily series of request counts for each of the
+ project's access tokens, oldest first. Every day in the window is
+ present, so a gap reads as zero rather than missing.
+
+ Revoked tokens are included, because the traffic they made before
+ revocation is usually the reason you are looking.
+
+ `days` defaults to 30 and is capped at 60, which is also how long per-day
+ counts are retained — a longer window cannot be answered.
+
+ A platform token sees every token in the project. A project access token
+ sees only its own row, so it can watch its own traffic without being
+ able to enumerate the project's other credentials by name.
+
+ Args:
+ id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | list[ProjectAccessTokenUsage]]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+days=days,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Error | list[ProjectAccessTokenUsage] | None:
+ """ Per-day request counts for every access token in a project
+
+ Returns a zero-filled daily series of request counts for each of the
+ project's access tokens, oldest first. Every day in the window is
+ present, so a gap reads as zero rather than missing.
+
+ Revoked tokens are included, because the traffic they made before
+ revocation is usually the reason you are looking.
+
+ `days` defaults to 30 and is capped at 60, which is also how long per-day
+ counts are retained — a longer window cannot be answered.
+
+ A platform token sees every token in the project. A project access token
+ sees only its own row, so it can watch its own traffic without being
+ able to enumerate the project's other credentials by name.
+
+ Args:
+ id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | list[ProjectAccessTokenUsage]
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+days=days,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Response[Error | list[ProjectAccessTokenUsage]]:
+ """ Per-day request counts for every access token in a project
+
+ Returns a zero-filled daily series of request counts for each of the
+ project's access tokens, oldest first. Every day in the window is
+ present, so a gap reads as zero rather than missing.
+
+ Revoked tokens are included, because the traffic they made before
+ revocation is usually the reason you are looking.
+
+ `days` defaults to 30 and is capped at 60, which is also how long per-day
+ counts are retained — a longer window cannot be answered.
+
+ A platform token sees every token in the project. A project access token
+ sees only its own row, so it can watch its own traffic without being
+ able to enumerate the project's other credentials by name.
+
+ Args:
+ id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | list[ProjectAccessTokenUsage]]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+days=days,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ days: int | Unset = 30,
+
+) -> Error | list[ProjectAccessTokenUsage] | None:
+ """ Per-day request counts for every access token in a project
+
+ Returns a zero-filled daily series of request counts for each of the
+ project's access tokens, oldest first. Every day in the window is
+ present, so a gap reads as zero rather than missing.
+
+ Revoked tokens are included, because the traffic they made before
+ revocation is usually the reason you are looking.
+
+ `days` defaults to 30 and is capped at 60, which is also how long per-day
+ counts are retained — a longer window cannot be answered.
+
+ A platform token sees every token in the project. A project access token
+ sees only its own row, so it can watch its own traffic without being
+ able to enumerate the project's other credentials by name.
+
+ Args:
+ id (UUID):
+ days (int | Unset): Default: 30.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | list[ProjectAccessTokenUsage]
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+days=days,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/project_access_tokens/revoke_project_access_token.py b/src/volcano_sdk/_generated/api/project_access_tokens/revoke_project_access_token.py
new file mode 100644
index 00000000..d285d878
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/project_access_tokens/revoke_project_access_token.py
@@ -0,0 +1,262 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ token_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "delete",
+ "url": "/projects/{id}/access-tokens/{token_id}".format(id=quote(str(id), safe=""),token_id=quote(str(token_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None:
+ if response.status_code == 204:
+ response_204 = cast(Any, None)
+ return response_204
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = Error.from_dict(response.json())
+
+
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = Error.from_dict(response.json())
+
+
+
+ return response_409
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Revoke a project access token
+
+ Revokes the token. It stops authenticating immediately in the region
+ handling this call and within seconds across Volcano's other regions.
+
+ The record is kept rather than deleted, so the token's name, prefix, last
+ use, and request history stay available — which is what you need if you
+ are revoking because a secret leaked. Revoking an already-revoked token
+ succeeds.
+
+ Revoking does not undo anything the token already did. Treat whatever it
+ could reach as exposed and rotate accordingly.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+token_id=token_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Revoke a project access token
+
+ Revokes the token. It stops authenticating immediately in the region
+ handling this call and within seconds across Volcano's other regions.
+
+ The record is kept rather than deleted, so the token's name, prefix, last
+ use, and request history stay available — which is what you need if you
+ are revoking because a secret leaked. Revoking an already-revoked token
+ succeeds.
+
+ Revoking does not undo anything the token already did. Treat whatever it
+ could reach as exposed and rotate accordingly.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ id=id,
+token_id=token_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Revoke a project access token
+
+ Revokes the token. It stops authenticating immediately in the region
+ handling this call and within seconds across Volcano's other regions.
+
+ The record is kept rather than deleted, so the token's name, prefix, last
+ use, and request history stay available — which is what you need if you
+ are revoking because a secret leaked. Revoking an already-revoked token
+ succeeds.
+
+ Revoking does not undo anything the token already did. Treat whatever it
+ could reach as exposed and rotate accordingly.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+token_id=token_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ token_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Revoke a project access token
+
+ Revokes the token. It stops authenticating immediately in the region
+ handling this call and within seconds across Volcano's other regions.
+
+ The record is kept rather than deleted, so the token's name, prefix, last
+ use, and request history stay available — which is what you need if you
+ are revoking because a secret leaked. Revoking an already-revoked token
+ succeeds.
+
+ Revoking does not undo anything the token already did. Treat whatever it
+ could reach as exposed and rotate accordingly.
+
+ Requires a platform token.
+
+ Args:
+ id (UUID):
+ token_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+token_id=token_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/projects/get_project_config.py b/src/volcano_sdk/_generated/api/projects/get_project_config.py
index b580d7be..8efca9ba 100644
--- a/src/volcano_sdk/_generated/api/projects/get_project_config.py
+++ b/src/volcano_sdk/_generated/api/projects/get_project_config.py
@@ -110,8 +110,8 @@ def sync_detailed(
`?format=yaml`; the YAML is returned verbatim as the raw response body
(`Content-Type: application/yaml`) and is meant to be saved as-is.
Variable values and write-only secrets (SMTP password, OAuth client secrets, TLS material)
- are omitted from the export; shared_variables contains names only; the YAML rendering adds a header
- comment
+ are omitted from the export; shared_variables and frontend_shared_variables contain names only; the
+ YAML rendering adds a header comment
describing how to set them via CLI environment interpolation.
Args:
@@ -154,8 +154,8 @@ def sync(
`?format=yaml`; the YAML is returned verbatim as the raw response body
(`Content-Type: application/yaml`) and is meant to be saved as-is.
Variable values and write-only secrets (SMTP password, OAuth client secrets, TLS material)
- are omitted from the export; shared_variables contains names only; the YAML rendering adds a header
- comment
+ are omitted from the export; shared_variables and frontend_shared_variables contain names only; the
+ YAML rendering adds a header comment
describing how to set them via CLI environment interpolation.
Args:
@@ -193,8 +193,8 @@ async def asyncio_detailed(
`?format=yaml`; the YAML is returned verbatim as the raw response body
(`Content-Type: application/yaml`) and is meant to be saved as-is.
Variable values and write-only secrets (SMTP password, OAuth client secrets, TLS material)
- are omitted from the export; shared_variables contains names only; the YAML rendering adds a header
- comment
+ are omitted from the export; shared_variables and frontend_shared_variables contain names only; the
+ YAML rendering adds a header comment
describing how to set them via CLI environment interpolation.
Args:
@@ -237,8 +237,8 @@ async def asyncio(
`?format=yaml`; the YAML is returned verbatim as the raw response body
(`Content-Type: application/yaml`) and is meant to be saved as-is.
Variable values and write-only secrets (SMTP password, OAuth client secrets, TLS material)
- are omitted from the export; shared_variables contains names only; the YAML rendering adds a header
- comment
+ are omitted from the export; shared_variables and frontend_shared_variables contain names only; the
+ YAML rendering adds a header comment
describing how to set them via CLI environment interpolation.
Args:
diff --git a/src/volcano_sdk/_generated/api/projects/replace_frontend_shared_variables.py b/src/volcano_sdk/_generated/api/projects/replace_frontend_shared_variables.py
new file mode 100644
index 00000000..5d9dae7d
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/projects/replace_frontend_shared_variables.py
@@ -0,0 +1,242 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.replace_frontend_shared_variables_body import ReplaceFrontendSharedVariablesBody
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ body: ReplaceFrontendSharedVariablesBody,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "put",
+ "url": "/projects/{id}/frontend-shared-variables".format(id=quote(str(id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None:
+ if response.status_code == 204:
+ response_204 = cast(Any, None)
+ return response_204
+
+ if response.status_code == 400:
+ response_400 = Error.from_dict(response.json())
+
+
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = cast(Any, None)
+ return response_401
+
+ if response.status_code == 404:
+ response_404 = cast(Any, None)
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = Error.from_dict(response.json())
+
+
+
+ return response_409
+
+ if response.status_code == 413:
+ response_413 = Error.from_dict(response.json())
+
+
+
+ return response_413
+
+ if response.status_code == 500:
+ response_500 = cast(Any, None)
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: ReplaceFrontendSharedVariablesBody,
+
+) -> Response[Any | Error]:
+ """ Replace frontend shared variable names
+
+ Atomically replaces the complete shared frontend-variable list without
+ changing values. Names must already exist. Validates final affected
+ frontend environments before membership or propagation side effects.
+ An empty list clears membership. Omitted names remain stored outside the frontend shared list.
+
+ Args:
+ id (UUID):
+ body (ReplaceFrontendSharedVariablesBody):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: ReplaceFrontendSharedVariablesBody,
+
+) -> Any | Error | None:
+ """ Replace frontend shared variable names
+
+ Atomically replaces the complete shared frontend-variable list without
+ changing values. Names must already exist. Validates final affected
+ frontend environments before membership or propagation side effects.
+ An empty list clears membership. Omitted names remain stored outside the frontend shared list.
+
+ Args:
+ id (UUID):
+ body (ReplaceFrontendSharedVariablesBody):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: ReplaceFrontendSharedVariablesBody,
+
+) -> Response[Any | Error]:
+ """ Replace frontend shared variable names
+
+ Atomically replaces the complete shared frontend-variable list without
+ changing values. Names must already exist. Validates final affected
+ frontend environments before membership or propagation side effects.
+ An empty list clears membership. Omitted names remain stored outside the frontend shared list.
+
+ Args:
+ id (UUID):
+ body (ReplaceFrontendSharedVariablesBody):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: ReplaceFrontendSharedVariablesBody,
+
+) -> Any | Error | None:
+ """ Replace frontend shared variable names
+
+ Atomically replaces the complete shared frontend-variable list without
+ changing values. Names must already exist. Validates final affected
+ frontend environments before membership or propagation side effects.
+ An empty list clears membership. Omitted names remain stored outside the frontend shared list.
+
+ Args:
+ id (UUID):
+ body (ReplaceFrontendSharedVariablesBody):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/__init__.py b/src/volcano_sdk/_generated/api/sandboxes/__init__.py
new file mode 100644
index 00000000..c9921b5f
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/__init__.py
@@ -0,0 +1 @@
+""" Contains endpoint functions for accessing the API """
diff --git a/src/volcano_sdk/_generated/api/sandboxes/create_sandbox.py b/src/volcano_sdk/_generated/api/sandboxes/create_sandbox.py
new file mode 100644
index 00000000..1a817999
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/create_sandbox.py
@@ -0,0 +1,210 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.create_sandbox_template_request import CreateSandboxTemplateRequest
+from ...models.error import Error
+from ...models.sandbox_template import SandboxTemplate
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ body: CreateSandboxTemplateRequest,
+ idempotency_key: UUID | str,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ headers["Idempotency-Key"] = str(idempotency_key)
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/projects/{id}/sandboxes".format(id=quote(str(id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxTemplate:
+ if response.status_code == 201:
+ response_201 = SandboxTemplate.from_dict(response.json())
+
+
+
+ return response_201
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxTemplate]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateSandboxTemplateRequest,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxTemplate]:
+ """ Create a sandbox template from a verified preset
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (CreateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplate]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateSandboxTemplateRequest,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxTemplate | None:
+ """ Create a sandbox template from a verified preset
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (CreateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplate
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateSandboxTemplateRequest,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxTemplate]:
+ """ Create a sandbox template from a verified preset
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (CreateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplate]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: CreateSandboxTemplateRequest,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxTemplate | None:
+ """ Create a sandbox template from a verified preset
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (CreateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplate
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/create_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/create_sandbox_session.py
new file mode 100644
index 00000000..0c6fc248
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/create_sandbox_session.py
@@ -0,0 +1,210 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_session import SandboxSession
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ headers["Idempotency-Key"] = str(idempotency_key)
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/projects/{id}/sandbox-sessions".format(id=quote(str(id), safe=""),),
+ }
+
+
+ _kwargs["json"] = body
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxSession:
+ if response.status_code == 201:
+ response_201 = SandboxSession.from_dict(response.json())
+
+
+
+ return response_201
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxSession]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxSession]:
+ """ Start a sandbox session
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxSession | None:
+ """ Start a sandbox session
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxSession]:
+ """ Start a sandbox session
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxSession | None:
+ """ Start a sandbox session
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/create_sandbox_session_access.py b/src/volcano_sdk/_generated/api/sandboxes/create_sandbox_session_access.py
new file mode 100644
index 00000000..d1851206
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/create_sandbox_session_access.py
@@ -0,0 +1,195 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_access import SandboxAccess
+from ...models.sandbox_access_request import SandboxAccessRequest
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+ *,
+ body: SandboxAccessRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/sandbox-sessions/{session_id}/access".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxAccess:
+ if response.status_code == 200:
+ response_200 = SandboxAccess.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxAccess]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxAccessRequest,
+
+) -> Response[Error | SandboxAccess]:
+ """ Issue a short-lived port-scoped access credential
+
+ Args:
+ session_id (UUID):
+ body (SandboxAccessRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxAccess]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxAccessRequest,
+
+) -> Error | SandboxAccess | None:
+ """ Issue a short-lived port-scoped access credential
+
+ Args:
+ session_id (UUID):
+ body (SandboxAccessRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxAccess
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxAccessRequest,
+
+) -> Response[Error | SandboxAccess]:
+ """ Issue a short-lived port-scoped access credential
+
+ Args:
+ session_id (UUID):
+ body (SandboxAccessRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxAccess]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxAccessRequest,
+
+) -> Error | SandboxAccess | None:
+ """ Issue a short-lived port-scoped access credential
+
+ Args:
+ session_id (UUID):
+ body (SandboxAccessRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxAccess
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/delete_sandbox.py b/src/volcano_sdk/_generated/api/sandboxes/delete_sandbox.py
new file mode 100644
index 00000000..27328e62
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/delete_sandbox.py
@@ -0,0 +1,184 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "delete",
+ "url": "/projects/{id}/sandboxes/{sandbox_id}".format(id=quote(str(id), safe=""),sandbox_id=quote(str(sandbox_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error:
+ if response.status_code == 202:
+ response_202 = cast(Any, None)
+ return response_202
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Retire a template and terminate its sessions
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Retire a template and terminate its sessions
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Retire a template and terminate its sessions
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Retire a template and terminate its sessions
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/execute_sandbox.py b/src/volcano_sdk/_generated/api/sandboxes/execute_sandbox.py
new file mode 100644
index 00000000..e219d4e7
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/execute_sandbox.py
@@ -0,0 +1,210 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_execution_result import SandboxExecutionResult
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ headers["Idempotency-Key"] = str(idempotency_key)
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/projects/{id}/sandbox-executions".format(id=quote(str(id), safe=""),),
+ }
+
+
+ _kwargs["json"] = body
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxExecutionResult:
+ if response.status_code == 200:
+ response_200 = SandboxExecutionResult.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxExecutionResult]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxExecutionResult]:
+ """ Execute once and return after confirmed termination
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxExecutionResult]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxExecutionResult | None:
+ """ Execute once and return after confirmed termination
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxExecutionResult
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxExecutionResult]:
+ """ Execute once and return after confirmed termination
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxExecutionResult]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: Any,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxExecutionResult | None:
+ """ Execute once and return after confirmed termination
+
+ Args:
+ id (UUID):
+ idempotency_key (UUID):
+ body (Any):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxExecutionResult
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/execute_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/execute_sandbox_session.py
new file mode 100644
index 00000000..21516bad
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/execute_sandbox_session.py
@@ -0,0 +1,210 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_command_request import SandboxCommandRequest
+from ...models.sandbox_command_result import SandboxCommandResult
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+ *,
+ body: SandboxCommandRequest,
+ idempotency_key: UUID | str,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ headers["Idempotency-Key"] = str(idempotency_key)
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/sandbox-sessions/{session_id}/exec".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxCommandResult:
+ if response.status_code == 200:
+ response_200 = SandboxCommandResult.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxCommandResult]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxCommandRequest,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxCommandResult]:
+ """ Execute a command within a session
+
+ Args:
+ session_id (UUID):
+ idempotency_key (UUID):
+ body (SandboxCommandRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxCommandResult]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxCommandRequest,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxCommandResult | None:
+ """ Execute a command within a session
+
+ Args:
+ session_id (UUID):
+ idempotency_key (UUID):
+ body (SandboxCommandRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxCommandResult
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxCommandRequest,
+ idempotency_key: UUID | str,
+
+) -> Response[Error | SandboxCommandResult]:
+ """ Execute a command within a session
+
+ Args:
+ session_id (UUID):
+ idempotency_key (UUID):
+ body (SandboxCommandRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxCommandResult]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+idempotency_key=idempotency_key,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxCommandRequest,
+ idempotency_key: UUID | str,
+
+) -> Error | SandboxCommandResult | None:
+ """ Execute a command within a session
+
+ Args:
+ session_id (UUID):
+ idempotency_key (UUID):
+ body (SandboxCommandRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxCommandResult
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+idempotency_key=idempotency_key,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/get_sandbox.py b/src/volcano_sdk/_generated/api/sandboxes/get_sandbox.py
new file mode 100644
index 00000000..3076978f
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/get_sandbox.py
@@ -0,0 +1,188 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_template import SandboxTemplate
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/sandboxes/{sandbox_id}".format(id=quote(str(id), safe=""),sandbox_id=quote(str(sandbox_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxTemplate:
+ if response.status_code == 200:
+ response_200 = SandboxTemplate.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxTemplate]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxTemplate]:
+ """ Get a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplate]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxTemplate | None:
+ """ Get a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplate
+ """
+
+
+ return sync_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxTemplate]:
+ """ Get a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplate]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxTemplate | None:
+ """ Get a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplate
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/get_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/get_sandbox_session.py
new file mode 100644
index 00000000..78a6fdfc
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/get_sandbox_session.py
@@ -0,0 +1,175 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_session import SandboxSession
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/sandbox-sessions/{session_id}".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxSession:
+ if response.status_code == 200:
+ response_200 = SandboxSession.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxSession]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Get a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Get a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Get a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Get a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/grant_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/grant_sandbox_session.py
new file mode 100644
index 00000000..d97cee60
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/grant_sandbox_session.py
@@ -0,0 +1,204 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_subject_grant_request import SandboxSubjectGrantRequest
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ body: SandboxSubjectGrantRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "put",
+ "url": "/sandbox-sessions/{session_id}/grants/{subject_id}".format(session_id=quote(str(session_id), safe=""),subject_id=quote(str(subject_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error:
+ if response.status_code == 204:
+ response_204 = cast(Any, None)
+ return response_204
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxSubjectGrantRequest,
+
+) -> Response[Any | Error]:
+ """ Authorize an authenticated project user for this session
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+ body (SandboxSubjectGrantRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+subject_id=subject_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxSubjectGrantRequest,
+
+) -> Any | Error | None:
+ """ Authorize an authenticated project user for this session
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+ body (SandboxSubjectGrantRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+subject_id=subject_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxSubjectGrantRequest,
+
+) -> Response[Any | Error]:
+ """ Authorize an authenticated project user for this session
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+ body (SandboxSubjectGrantRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+subject_id=subject_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxSubjectGrantRequest,
+
+) -> Any | Error | None:
+ """ Authorize an authenticated project user for this session
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+ body (SandboxSubjectGrantRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+subject_id=subject_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_deployments.py b/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_deployments.py
new file mode 100644
index 00000000..a23cefb0
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_deployments.py
@@ -0,0 +1,225 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_deployment_page import SandboxDeploymentPage
+from ...types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+
+
+
+
+ params: dict[str, Any] = {}
+
+ params["limit"] = limit
+
+ params["cursor"] = cursor
+
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/sandboxes/{sandbox_id}/deployments".format(id=quote(str(id), safe=""),sandbox_id=quote(str(sandbox_id), safe=""),),
+ "params": params,
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxDeploymentPage:
+ if response.status_code == 200:
+ response_200 = SandboxDeploymentPage.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxDeploymentPage]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Response[Error | SandboxDeploymentPage]:
+ """ List sandbox deployment history
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxDeploymentPage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+limit=limit,
+cursor=cursor,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Error | SandboxDeploymentPage | None:
+ """ List sandbox deployment history
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxDeploymentPage
+ """
+
+
+ return sync_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+limit=limit,
+cursor=cursor,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Response[Error | SandboxDeploymentPage]:
+ """ List sandbox deployment history
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxDeploymentPage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+limit=limit,
+cursor=cursor,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Error | SandboxDeploymentPage | None:
+ """ List sandbox deployment history
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxDeploymentPage
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+limit=limit,
+cursor=cursor,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_presets.py b/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_presets.py
new file mode 100644
index 00000000..f72c6573
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_presets.py
@@ -0,0 +1,153 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_preset_list import SandboxPresetList
+from typing import cast
+
+
+
+def request_kwargs(
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/sandboxes/presets",
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxPresetList:
+ if response.status_code == 200:
+ response_200 = SandboxPresetList.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxPresetList]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+
+) -> Response[Error | SandboxPresetList]:
+ """ List available sandbox presets
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxPresetList]
+ """
+
+
+ kwargs = request_kwargs(
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+
+) -> Error | SandboxPresetList | None:
+ """ List available sandbox presets
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxPresetList
+ """
+
+
+ return sync_detailed(
+ client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+
+) -> Response[Error | SandboxPresetList]:
+ """ List available sandbox presets
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxPresetList]
+ """
+
+
+ kwargs = request_kwargs(
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+
+) -> Error | SandboxPresetList | None:
+ """ List available sandbox presets
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxPresetList
+ """
+
+
+ return (await asyncio_detailed(
+ client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_sessions.py b/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_sessions.py
new file mode 100644
index 00000000..f52f42f7
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/list_sandbox_sessions.py
@@ -0,0 +1,212 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_session_page import SandboxSessionPage
+from ...types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+
+
+
+
+ params: dict[str, Any] = {}
+
+ params["limit"] = limit
+
+ params["cursor"] = cursor
+
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/sandbox-sessions".format(id=quote(str(id), safe=""),),
+ "params": params,
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxSessionPage:
+ if response.status_code == 200:
+ response_200 = SandboxSessionPage.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxSessionPage]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Response[Error | SandboxSessionPage]:
+ """ List project sandbox sessions
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSessionPage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+limit=limit,
+cursor=cursor,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Error | SandboxSessionPage | None:
+ """ List project sandbox sessions
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSessionPage
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+limit=limit,
+cursor=cursor,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Response[Error | SandboxSessionPage]:
+ """ List project sandbox sessions
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSessionPage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+limit=limit,
+cursor=cursor,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Error | SandboxSessionPage | None:
+ """ List project sandbox sessions
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSessionPage
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+limit=limit,
+cursor=cursor,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/list_sandboxes.py b/src/volcano_sdk/_generated/api/sandboxes/list_sandboxes.py
new file mode 100644
index 00000000..c0e8eac8
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/list_sandboxes.py
@@ -0,0 +1,212 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_template_page import SandboxTemplatePage
+from ...types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ *,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+
+
+
+
+ params: dict[str, Any] = {}
+
+ params["limit"] = limit
+
+ params["cursor"] = cursor
+
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/projects/{id}/sandboxes".format(id=quote(str(id), safe=""),),
+ "params": params,
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxTemplatePage:
+ if response.status_code == 200:
+ response_200 = SandboxTemplatePage.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxTemplatePage]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Response[Error | SandboxTemplatePage]:
+ """ List sandbox templates
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplatePage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+limit=limit,
+cursor=cursor,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Error | SandboxTemplatePage | None:
+ """ List sandbox templates
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplatePage
+ """
+
+
+ return sync_detailed(
+ id=id,
+client=client,
+limit=limit,
+cursor=cursor,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Response[Error | SandboxTemplatePage]:
+ """ List sandbox templates
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplatePage]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+limit=limit,
+cursor=cursor,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ limit: int | Unset = 10,
+ cursor: str | Unset = UNSET,
+
+) -> Error | SandboxTemplatePage | None:
+ """ List sandbox templates
+
+ Args:
+ id (UUID):
+ limit (int | Unset): Default: 10.
+ cursor (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplatePage
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+client=client,
+limit=limit,
+cursor=cursor,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/read_sandbox_session_file.py b/src/volcano_sdk/_generated/api/sandboxes/read_sandbox_session_file.py
new file mode 100644
index 00000000..9796a2c2
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/read_sandbox_session_file.py
@@ -0,0 +1,195 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_file_read_request import SandboxFileReadRequest
+from ...models.sandbox_file_result import SandboxFileResult
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+ *,
+ body: SandboxFileReadRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/sandbox-sessions/{session_id}/files/read".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxFileResult:
+ if response.status_code == 200:
+ response_200 = SandboxFileResult.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxFileResult]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileReadRequest,
+
+) -> Response[Error | SandboxFileResult]:
+ """ Read a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileReadRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxFileResult]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileReadRequest,
+
+) -> Error | SandboxFileResult | None:
+ """ Read a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileReadRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxFileResult
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileReadRequest,
+
+) -> Response[Error | SandboxFileResult]:
+ """ Read a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileReadRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxFileResult]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileReadRequest,
+
+) -> Error | SandboxFileResult | None:
+ """ Read a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileReadRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxFileResult
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/resume_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/resume_sandbox_session.py
new file mode 100644
index 00000000..c2a09265
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/resume_sandbox_session.py
@@ -0,0 +1,175 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_session import SandboxSession
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/sandbox-sessions/{session_id}/resume".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxSession:
+ if response.status_code == 202:
+ response_202 = SandboxSession.from_dict(response.json())
+
+
+
+ return response_202
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxSession]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Resume a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Resume a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Resume a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Resume a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/revoke_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/revoke_sandbox_session.py
new file mode 100644
index 00000000..8a070a58
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/revoke_sandbox_session.py
@@ -0,0 +1,184 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "delete",
+ "url": "/sandbox-sessions/{session_id}/grants/{subject_id}".format(session_id=quote(str(session_id), safe=""),subject_id=quote(str(subject_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error:
+ if response.status_code == 204:
+ response_204 = cast(Any, None)
+ return response_204
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Revoke a project user session grant
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+subject_id=subject_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Revoke a project user session grant
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+subject_id=subject_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Any | Error]:
+ """ Revoke a project user session grant
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+subject_id=subject_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ subject_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Any | Error | None:
+ """ Revoke a project user session grant
+
+ Args:
+ session_id (UUID):
+ subject_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+subject_id=subject_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/suspend_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/suspend_sandbox_session.py
new file mode 100644
index 00000000..8503d332
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/suspend_sandbox_session.py
@@ -0,0 +1,175 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_session import SandboxSession
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/sandbox-sessions/{session_id}/suspend".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxSession:
+ if response.status_code == 202:
+ response_202 = SandboxSession.from_dict(response.json())
+
+
+
+ return response_202
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxSession]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Suspend a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Suspend a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Suspend a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Suspend a sandbox session
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/terminate_sandbox_session.py b/src/volcano_sdk/_generated/api/sandboxes/terminate_sandbox_session.py
new file mode 100644
index 00000000..c90d49ea
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/terminate_sandbox_session.py
@@ -0,0 +1,175 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_session import SandboxSession
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+
+) -> dict[str, Any]:
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "delete",
+ "url": "/sandbox-sessions/{session_id}".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxSession:
+ if response.status_code == 202:
+ response_202 = SandboxSession.from_dict(response.json())
+
+
+
+ return response_202
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxSession]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Request sandbox termination
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Request sandbox termination
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Response[Error | SandboxSession]:
+ """ Request sandbox termination
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxSession]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+
+) -> Error | SandboxSession | None:
+ """ Request sandbox termination
+
+ Args:
+ session_id (UUID):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxSession
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/update_sandbox.py b/src/volcano_sdk/_generated/api/sandboxes/update_sandbox.py
new file mode 100644
index 00000000..bde279d5
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/update_sandbox.py
@@ -0,0 +1,208 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_template import SandboxTemplate
+from ...models.update_sandbox_template_request import UpdateSandboxTemplateRequest
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ body: UpdateSandboxTemplateRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "patch",
+ "url": "/projects/{id}/sandboxes/{sandbox_id}".format(id=quote(str(id), safe=""),sandbox_id=quote(str(sandbox_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | SandboxTemplate:
+ if response.status_code == 200:
+ response_200 = SandboxTemplate.from_dict(response.json())
+
+
+
+ return response_200
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | SandboxTemplate]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: UpdateSandboxTemplateRequest,
+
+) -> Response[Error | SandboxTemplate]:
+ """ Rename a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ body (UpdateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplate]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: UpdateSandboxTemplateRequest,
+
+) -> Error | SandboxTemplate | None:
+ """ Rename a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ body (UpdateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplate
+ """
+
+
+ return sync_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: UpdateSandboxTemplateRequest,
+
+) -> Response[Error | SandboxTemplate]:
+ """ Rename a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ body (UpdateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Error | SandboxTemplate]
+ """
+
+
+ kwargs = request_kwargs(
+ id=id,
+sandbox_id=sandbox_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ id: UUID | str,
+ sandbox_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: UpdateSandboxTemplateRequest,
+
+) -> Error | SandboxTemplate | None:
+ """ Rename a sandbox template
+
+ Args:
+ id (UUID):
+ sandbox_id (UUID):
+ body (UpdateSandboxTemplateRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Error | SandboxTemplate
+ """
+
+
+ return (await asyncio_detailed(
+ id=id,
+sandbox_id=sandbox_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/sandboxes/write_sandbox_session_file.py b/src/volcano_sdk/_generated/api/sandboxes/write_sandbox_session_file.py
new file mode 100644
index 00000000..bc23f1f2
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/sandboxes/write_sandbox_session_file.py
@@ -0,0 +1,191 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.sandbox_file_write_request import SandboxFileWriteRequest
+from typing import cast
+from uuid import UUID
+
+
+
+def request_kwargs(
+ session_id: UUID | str,
+ *,
+ body: SandboxFileWriteRequest,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/sandbox-sessions/{session_id}/files/write".format(session_id=quote(str(session_id), safe=""),),
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error:
+ if response.status_code == 204:
+ response_204 = cast(Any, None)
+ return response_204
+
+ response_default = Error.from_dict(response.json())
+
+
+
+ return response_default
+
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileWriteRequest,
+
+) -> Response[Any | Error]:
+ """ Write a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileWriteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileWriteRequest,
+
+) -> Any | Error | None:
+ """ Write a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileWriteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileWriteRequest,
+
+) -> Response[Any | Error]:
+ """ Write a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileWriteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ session_id=session_id,
+body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ session_id: UUID | str,
+ *,
+ client: AuthenticatedClient,
+ body: SandboxFileWriteRequest,
+
+) -> Any | Error | None:
+ """ Write a workspace file
+
+ Args:
+ session_id (UUID):
+ body (SandboxFileWriteRequest):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ session_id=session_id,
+client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/service_keys/regenerate_service_key.py b/src/volcano_sdk/_generated/api/service_keys/regenerate_service_key.py
index edb97b55..25705f10 100644
--- a/src/volcano_sdk/_generated/api/service_keys/regenerate_service_key.py
+++ b/src/volcano_sdk/_generated/api/service_keys/regenerate_service_key.py
@@ -68,7 +68,7 @@ def sync_detailed(
""" Regenerate service key
Generate new JWT value for existing key.
- The old key is immediately invalidated.
+ The old key stops working within a few seconds.
Update your backend services with the new key before regenerating in production.
Args:
@@ -106,7 +106,7 @@ def sync(
""" Regenerate service key
Generate new JWT value for existing key.
- The old key is immediately invalidated.
+ The old key stops working within a few seconds.
Update your backend services with the new key before regenerating in production.
Args:
@@ -139,7 +139,7 @@ async def asyncio_detailed(
""" Regenerate service key
Generate new JWT value for existing key.
- The old key is immediately invalidated.
+ The old key stops working within a few seconds.
Update your backend services with the new key before regenerating in production.
Args:
@@ -177,7 +177,7 @@ async def asyncio(
""" Regenerate service key
Generate new JWT value for existing key.
- The old key is immediately invalidated.
+ The old key stops working within a few seconds.
Update your backend services with the new key before regenerating in production.
Args:
diff --git a/src/volcano_sdk/_generated/api/system/call_mcp.py b/src/volcano_sdk/_generated/api/system/call_mcp.py
new file mode 100644
index 00000000..f63692dd
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/system/call_mcp.py
@@ -0,0 +1,277 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.call_mcp_body import CallMCPBody
+from ...models.call_mcp_response_200 import CallMCPResponse200
+from ...models.error import Error
+from typing import cast
+
+
+
+def request_kwargs(
+ *,
+ body: CallMCPBody,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/mcp",
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | CallMCPResponse200 | Error | None:
+ if response.status_code == 200:
+ response_200 = CallMCPResponse200.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 202:
+ response_202 = cast(Any, None)
+ return response_202
+
+ if response.status_code == 401:
+ response_401 = Error.from_dict(response.json())
+
+
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = Error.from_dict(response.json())
+
+
+
+ return response_403
+
+ if response.status_code == 413:
+ response_413 = cast(Any, None)
+ return response_413
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | CallMCPResponse200 | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient,
+ body: CallMCPBody,
+
+) -> Response[Any | CallMCPResponse200 | Error]:
+ """ Model Context Protocol endpoint
+
+ Streamable-HTTP MCP endpoint: one JSON-RPC 2.0 object per request, one
+ response per request. There is no server-to-client stream, so a `GET`
+ returns `405`, and a batched array is rejected.
+
+ Authenticated with a **project access token**. The endpoint takes its
+ project from the credential, so a platform token is refused with `403` —
+ it names no project, and letting a tool argument choose one would hand an
+ agent its own blast radius.
+
+ Scope carries over from the REST API. A `read_only` token is not offered
+ mutating tools or credential-returning reads, and is refused if it calls
+ one anyway. Revoking the token ends MCP access on the same path it ends
+ API access.
+
+ Methods: `initialize`, `notifications/initialized`, `ping`,
+ `tools/list`, `tools/call`. See the
+ [MCP guide](https://docs.volcano.dev/platform/interfaces/mcp) for the
+ tool surface and client configuration.
+
+ Args:
+ body (CallMCPBody): A JSON-RPC 2.0 request object.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | CallMCPResponse200 | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ body=body,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ *,
+ client: AuthenticatedClient,
+ body: CallMCPBody,
+
+) -> Any | CallMCPResponse200 | Error | None:
+ """ Model Context Protocol endpoint
+
+ Streamable-HTTP MCP endpoint: one JSON-RPC 2.0 object per request, one
+ response per request. There is no server-to-client stream, so a `GET`
+ returns `405`, and a batched array is rejected.
+
+ Authenticated with a **project access token**. The endpoint takes its
+ project from the credential, so a platform token is refused with `403` —
+ it names no project, and letting a tool argument choose one would hand an
+ agent its own blast radius.
+
+ Scope carries over from the REST API. A `read_only` token is not offered
+ mutating tools or credential-returning reads, and is refused if it calls
+ one anyway. Revoking the token ends MCP access on the same path it ends
+ API access.
+
+ Methods: `initialize`, `notifications/initialized`, `ping`,
+ `tools/list`, `tools/call`. See the
+ [MCP guide](https://docs.volcano.dev/platform/interfaces/mcp) for the
+ tool surface and client configuration.
+
+ Args:
+ body (CallMCPBody): A JSON-RPC 2.0 request object.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | CallMCPResponse200 | Error
+ """
+
+
+ return sync_detailed(
+ client=client,
+body=body,
+
+ ).parsed
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient,
+ body: CallMCPBody,
+
+) -> Response[Any | CallMCPResponse200 | Error]:
+ """ Model Context Protocol endpoint
+
+ Streamable-HTTP MCP endpoint: one JSON-RPC 2.0 object per request, one
+ response per request. There is no server-to-client stream, so a `GET`
+ returns `405`, and a batched array is rejected.
+
+ Authenticated with a **project access token**. The endpoint takes its
+ project from the credential, so a platform token is refused with `403` —
+ it names no project, and letting a tool argument choose one would hand an
+ agent its own blast radius.
+
+ Scope carries over from the REST API. A `read_only` token is not offered
+ mutating tools or credential-returning reads, and is refused if it calls
+ one anyway. Revoking the token ends MCP access on the same path it ends
+ API access.
+
+ Methods: `initialize`, `notifications/initialized`, `ping`,
+ `tools/list`, `tools/call`. See the
+ [MCP guide](https://docs.volcano.dev/platform/interfaces/mcp) for the
+ tool surface and client configuration.
+
+ Args:
+ body (CallMCPBody): A JSON-RPC 2.0 request object.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | CallMCPResponse200 | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ body=body,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient,
+ body: CallMCPBody,
+
+) -> Any | CallMCPResponse200 | Error | None:
+ """ Model Context Protocol endpoint
+
+ Streamable-HTTP MCP endpoint: one JSON-RPC 2.0 object per request, one
+ response per request. There is no server-to-client stream, so a `GET`
+ returns `405`, and a batched array is rejected.
+
+ Authenticated with a **project access token**. The endpoint takes its
+ project from the credential, so a platform token is refused with `403` —
+ it names no project, and letting a tool argument choose one would hand an
+ agent its own blast radius.
+
+ Scope carries over from the REST API. A `read_only` token is not offered
+ mutating tools or credential-returning reads, and is refused if it calls
+ one anyway. Revoking the token ends MCP access on the same path it ends
+ API access.
+
+ Methods: `initialize`, `notifications/initialized`, `ping`,
+ `tools/list`, `tools/call`. See the
+ [MCP guide](https://docs.volcano.dev/platform/interfaces/mcp) for the
+ tool surface and client configuration.
+
+ Args:
+ body (CallMCPBody): A JSON-RPC 2.0 request object.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | CallMCPResponse200 | Error
+ """
+
+
+ return (await asyncio_detailed(
+ client=client,
+body=body,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/system/get_open_api_spec_json.py b/src/volcano_sdk/_generated/api/system/get_open_api_spec_json.py
new file mode 100644
index 00000000..1e4c9670
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/system/get_open_api_spec_json.py
@@ -0,0 +1,238 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.open_api_spec_document import OpenAPISpecDocument
+from ...types import UNSET, Unset
+from typing import cast
+
+
+
+def request_kwargs(
+ *,
+ if_none_match: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ if not isinstance(if_none_match, Unset):
+ headers["If-None-Match"] = if_none_match
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/openapi.json",
+ }
+
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | OpenAPISpecDocument | None:
+ if response.status_code == 200:
+ response_200 = OpenAPISpecDocument.from_dict(response.json())
+
+
+
+ return response_200
+
+ if response.status_code == 304:
+ response_304 = cast(Any, None)
+ return response_304
+
+ if response.status_code == 429:
+ response_429 = Error.from_dict(response.json())
+
+
+
+ return response_429
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error | OpenAPISpecDocument]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error | OpenAPISpecDocument]:
+ """ Fetch the OpenAPI specification as JSON
+
+ Returns this specification as a self-contained JSON document, with every
+ reference resolved. It is generated from the same document the server
+ validates requests against, so a client generated from it cannot
+ describe a different API than the one that answers.
+
+ No credential is required: a client generator fetches this by URL before
+ its user has a token, and every path here is already published in the
+ API reference.
+
+ The response carries a strong `ETag`; send it back as `If-None-Match` to
+ get `304 Not Modified` instead of the whole document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error | OpenAPISpecDocument]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | OpenAPISpecDocument | None:
+ """ Fetch the OpenAPI specification as JSON
+
+ Returns this specification as a self-contained JSON document, with every
+ reference resolved. It is generated from the same document the server
+ validates requests against, so a client generated from it cannot
+ describe a different API than the one that answers.
+
+ No credential is required: a client generator fetches this by URL before
+ its user has a token, and every path here is already published in the
+ API reference.
+
+ The response carries a strong `ETag`; send it back as `If-None-Match` to
+ get `304 Not Modified` instead of the whole document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error | OpenAPISpecDocument
+ """
+
+
+ return sync_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ ).parsed
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error | OpenAPISpecDocument]:
+ """ Fetch the OpenAPI specification as JSON
+
+ Returns this specification as a self-contained JSON document, with every
+ reference resolved. It is generated from the same document the server
+ validates requests against, so a client generated from it cannot
+ describe a different API than the one that answers.
+
+ No credential is required: a client generator fetches this by URL before
+ its user has a token, and every path here is already published in the
+ API reference.
+
+ The response carries a strong `ETag`; send it back as `If-None-Match` to
+ get `304 Not Modified` instead of the whole document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error | OpenAPISpecDocument]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | OpenAPISpecDocument | None:
+ """ Fetch the OpenAPI specification as JSON
+
+ Returns this specification as a self-contained JSON document, with every
+ reference resolved. It is generated from the same document the server
+ validates requests against, so a client generated from it cannot
+ describe a different API than the one that answers.
+
+ No credential is required: a client generator fetches this by URL before
+ its user has a token, and every path here is already published in the
+ API reference.
+
+ The response carries a strong `ETag`; send it back as `If-None-Match` to
+ get `304 Not Modified` instead of the whole document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error | OpenAPISpecDocument
+ """
+
+
+ return (await asyncio_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/system/get_open_api_spec_yaml.py b/src/volcano_sdk/_generated/api/system/get_open_api_spec_yaml.py
new file mode 100644
index 00000000..824e607c
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/system/get_open_api_spec_yaml.py
@@ -0,0 +1,202 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...models.open_api_spec_document import OpenAPISpecDocument
+from ...types import UNSET, Unset
+from typing import cast
+
+
+
+def request_kwargs(
+ *,
+ if_none_match: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ if not isinstance(if_none_match, Unset):
+ headers["If-None-Match"] = if_none_match
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/openapi.yaml",
+ }
+
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | OpenAPISpecDocument | None:
+ if response.status_code == 200:
+ response_200 = OpenAPISpecDocument.from_dict(response.content)
+
+
+
+ return response_200
+
+ if response.status_code == 304:
+ response_304 = cast(Any, None)
+ return response_304
+
+ if response.status_code == 429:
+ response_429 = Error.from_dict(response.json())
+
+
+
+ return response_429
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error | OpenAPISpecDocument]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error | OpenAPISpecDocument]:
+ """ Fetch the OpenAPI specification as YAML
+
+ The same document as `/openapi.json`, serialized as YAML for tools that
+ prefer it. See that operation for caching and authentication notes.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error | OpenAPISpecDocument]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | OpenAPISpecDocument | None:
+ """ Fetch the OpenAPI specification as YAML
+
+ The same document as `/openapi.json`, serialized as YAML for tools that
+ prefer it. See that operation for caching and authentication notes.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error | OpenAPISpecDocument
+ """
+
+
+ return sync_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ ).parsed
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error | OpenAPISpecDocument]:
+ """ Fetch the OpenAPI specification as YAML
+
+ The same document as `/openapi.json`, serialized as YAML for tools that
+ prefer it. See that operation for caching and authentication notes.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error | OpenAPISpecDocument]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | OpenAPISpecDocument | None:
+ """ Fetch the OpenAPI specification as YAML
+
+ The same document as `/openapi.json`, serialized as YAML for tools that
+ prefer it. See that operation for caching and authentication notes.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error | OpenAPISpecDocument
+ """
+
+
+ return (await asyncio_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/system/head_open_api_spec_json.py b/src/volcano_sdk/_generated/api/system/head_open_api_spec_json.py
new file mode 100644
index 00000000..b1686667
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/system/head_open_api_spec_json.py
@@ -0,0 +1,198 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...types import UNSET, Unset
+from typing import cast
+
+
+
+def request_kwargs(
+ *,
+ if_none_match: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ if not isinstance(if_none_match, Unset):
+ headers["If-None-Match"] = if_none_match
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "head",
+ "url": "/openapi.json",
+ }
+
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None:
+ if response.status_code == 200:
+ response_200 = cast(Any, None)
+ return response_200
+
+ if response.status_code == 304:
+ response_304 = cast(Any, None)
+ return response_304
+
+ if response.status_code == 429:
+ response_429 = Error.from_dict(response.json())
+
+
+
+ return response_429
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error]:
+ """ Check the JSON OpenAPI specification
+
+ The headers `GET /openapi.json` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | None:
+ """ Check the JSON OpenAPI specification
+
+ The headers `GET /openapi.json` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ ).parsed
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error]:
+ """ Check the JSON OpenAPI specification
+
+ The headers `GET /openapi.json` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | None:
+ """ Check the JSON OpenAPI specification
+
+ The headers `GET /openapi.json` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/api/system/head_open_api_spec_yaml.py b/src/volcano_sdk/_generated/api/system/head_open_api_spec_yaml.py
new file mode 100644
index 00000000..07eb2787
--- /dev/null
+++ b/src/volcano_sdk/_generated/api/system/head_open_api_spec_yaml.py
@@ -0,0 +1,198 @@
+from http import HTTPStatus
+from typing import Any, cast
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error import Error
+from ...types import UNSET, Unset
+from typing import cast
+
+
+
+def request_kwargs(
+ *,
+ if_none_match: str | Unset = UNSET,
+
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ if not isinstance(if_none_match, Unset):
+ headers["If-None-Match"] = if_none_match
+
+
+
+
+
+
+
+ _kwargs: dict[str, Any] = {
+ "method": "head",
+ "url": "/openapi.yaml",
+ }
+
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+
+def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None:
+ if response.status_code == 200:
+ response_200 = cast(Any, None)
+ return response_200
+
+ if response.status_code == 304:
+ response_304 = cast(Any, None)
+ return response_304
+
+ if response.status_code == 429:
+ response_429 = Error.from_dict(response.json())
+
+
+
+ return response_429
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error]:
+ """ Check the YAML OpenAPI specification
+
+ The headers `GET /openapi.yaml` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return build_response(client=client, response=response)
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | None:
+ """ Check the YAML OpenAPI specification
+
+ The headers `GET /openapi.yaml` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return sync_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ ).parsed
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Response[Any | Error]:
+ """ Check the YAML OpenAPI specification
+
+ The headers `GET /openapi.yaml` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | Error]
+ """
+
+
+ kwargs = request_kwargs(
+ if_none_match=if_none_match,
+
+ )
+
+ response = await client.get_async_httpx_client().request(
+ **kwargs
+ )
+
+ return build_response(client=client, response=response)
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+ if_none_match: str | Unset = UNSET,
+
+) -> Any | Error | None:
+ """ Check the YAML OpenAPI specification
+
+ The headers `GET /openapi.yaml` would return, so a cache can pick up the
+ current `ETag` without transferring the document.
+
+ Args:
+ if_none_match (str | Unset):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | Error
+ """
+
+
+ return (await asyncio_detailed(
+ client=client,
+if_none_match=if_none_match,
+
+ )).parsed
diff --git a/src/volcano_sdk/_generated/models/__init__.py b/src/volcano_sdk/_generated/models/__init__.py
index 80ce1ca5..ac3143ee 100644
--- a/src/volcano_sdk/_generated/models/__init__.py
+++ b/src/volcano_sdk/_generated/models/__init__.py
@@ -96,6 +96,13 @@
from .batch_function_deploy_failure import BatchFunctionDeployFailure
from .batch_function_deploy_failure_operation import BatchFunctionDeployFailureOperation
from .batch_function_deploy_response import BatchFunctionDeployResponse
+from .call_mcp_body import CallMCPBody
+from .call_mcp_body_jsonrpc import CallMCPBodyJsonrpc
+from .call_mcp_body_params import CallMCPBodyParams
+from .call_mcp_response_200 import CallMCPResponse200
+from .call_mcp_response_200_error import CallMCPResponse200Error
+from .call_mcp_response_200_jsonrpc import CallMCPResponse200Jsonrpc
+from .call_mcp_response_200_result import CallMCPResponse200Result
from .call_o_auth_provider_api_body import CallOAuthProviderAPIBody
from .call_o_auth_provider_api_body_body import CallOAuthProviderAPIBodyBody
from .call_o_auth_provider_api_body_method import CallOAuthProviderAPIBodyMethod
@@ -121,7 +128,9 @@
from .create_email_template_request_template_type import CreateEmailTemplateRequestTemplateType
from .create_frontend_body import CreateFrontendBody
from .create_frontend_body_framework import CreateFrontendBodyFramework
+from .create_frontend_body_variable_scope import CreateFrontendBodyVariableScope
from .create_frontend_custom_domain_request import CreateFrontendCustomDomainRequest
+from .create_frontend_function_route_request import CreateFrontendFunctionRouteRequest
from .create_function_body import CreateFunctionBody
from .create_function_body_runtime import CreateFunctionBodyRuntime
from .create_function_body_variable_scope import CreateFunctionBodyVariableScope
@@ -130,7 +139,11 @@
from .create_functions_batch_body import CreateFunctionsBatchBody
from .create_o_auth_config_request import CreateOAuthConfigRequest
from .create_o_auth_config_request_provider import CreateOAuthConfigRequestProvider
+from .create_project_access_token_request import CreateProjectAccessTokenRequest
from .create_project_request import CreateProjectRequest
+from .create_sandbox_template_request import CreateSandboxTemplateRequest
+from .create_sandbox_template_request_memory_mb import CreateSandboxTemplateRequestMemoryMb
+from .create_sandbox_template_request_preset import CreateSandboxTemplateRequestPreset
from .create_service_key_body import CreateServiceKeyBody
from .create_storage_bucket_request import CreateStorageBucketRequest
from .create_storage_policy_request import CreateStoragePolicyRequest
@@ -138,6 +151,7 @@
from .create_upload_session_request import CreateUploadSessionRequest
from .create_upload_session_response import CreateUploadSessionResponse
from .create_variable_request import CreateVariableRequest
+from .created_project_access_token import CreatedProjectAccessToken
from .database import Database
from .database_backup import DatabaseBackup
from .database_backup_list import DatabaseBackupList
@@ -210,10 +224,13 @@
from .frontend_domain_routing_record_record_type import FrontendDomainRoutingRecordRecordType
from .frontend_domain_verification_record import FrontendDomainVerificationRecord
from .frontend_framework import FrontendFramework
+from .frontend_function_route import FrontendFunctionRoute
+from .frontend_function_route_list import FrontendFunctionRouteList
from .frontend_status import FrontendStatus
from .frontend_usage_daily_entry import FrontendUsageDailyEntry
from .frontend_usage_data import FrontendUsageData
from .frontend_usage_history_response import FrontendUsageHistoryResponse
+from .frontend_variable_scope import FrontendVariableScope
from .function import Function
from .function_deployment import FunctionDeployment
from .function_deployment_deploy_source import FunctionDeploymentDeploySource
@@ -313,6 +330,7 @@
from .o_auth_config import OAuthConfig
from .o_auth_config_provider import OAuthConfigProvider
from .o_auth_error_response import OAuthErrorResponse
+from .open_api_spec_document import OpenAPISpecDocument
from .paginated_auth_users import PaginatedAuthUsers
from .paginated_databases import PaginatedDatabases
from .paginated_durable_executions import PaginatedDurableExecutions
@@ -321,6 +339,7 @@
from .paginated_frontends import PaginatedFrontends
from .paginated_function_deployments import PaginatedFunctionDeployments
from .paginated_functions import PaginatedFunctions
+from .paginated_project_access_tokens import PaginatedProjectAccessTokens
from .paginated_project_custom_domains import PaginatedProjectCustomDomains
from .paginated_project_deployments import PaginatedProjectDeployments
from .paginated_projects import PaginatedProjects
@@ -332,6 +351,12 @@
from .preview_auth_page_request import PreviewAuthPageRequest
from .preview_auth_page_response import PreviewAuthPageResponse
from .project import Project
+from .project_access_token import ProjectAccessToken
+from .project_access_token_scope import ProjectAccessTokenScope
+from .project_access_token_status import ProjectAccessTokenStatus
+from .project_access_token_token_source import ProjectAccessTokenTokenSource
+from .project_access_token_usage import ProjectAccessTokenUsage
+from .project_access_token_usage_daily_entry import ProjectAccessTokenUsageDailyEntry
from .project_config import ProjectConfig
from .project_config_apply_result import ProjectConfigApplyResult
from .project_config_apply_result_entry import ProjectConfigApplyResultEntry
@@ -366,6 +391,8 @@
from .project_config_email_template import ProjectConfigEmailTemplate
from .project_config_email_templates import ProjectConfigEmailTemplates
from .project_config_frontend import ProjectConfigFrontend
+from .project_config_frontend_function_route import ProjectConfigFrontendFunctionRoute
+from .project_config_frontend_variable_scope import ProjectConfigFrontendVariableScope
from .project_config_function import ProjectConfigFunction
from .project_config_function_openapi_spec_type_0 import ProjectConfigFunctionOpenapiSpecType0
from .project_config_function_variable_scope import ProjectConfigFunctionVariableScope
@@ -449,17 +476,46 @@
from .project_source_export_state_mode import ProjectSourceExportStateMode
from .project_status import ProjectStatus
from .project_usage_response import ProjectUsageResponse
+from .publish_sandbox_preset_request import PublishSandboxPresetRequest
+from .publish_sandbox_preset_request_memory_mb import PublishSandboxPresetRequestMemoryMb
+from .publish_sandbox_preset_request_preset import PublishSandboxPresetRequestPreset
from .realtime_config import RealtimeConfig
from .realtime_plan_limits import RealtimePlanLimits
from .realtime_stats import RealtimeStats
from .refresh_o_auth_provider_token_provider import RefreshOAuthProviderTokenProvider
from .refresh_o_auth_provider_token_response_200 import RefreshOAuthProviderTokenResponse200
from .render_default_managed_auth_page_action import RenderDefaultManagedAuthPageAction
+from .replace_frontend_shared_variables_body import ReplaceFrontendSharedVariablesBody
from .replace_shared_variables_body import ReplaceSharedVariablesBody
from .reset_database_password_response_200 import ResetDatabasePasswordResponse200
from .resolve_function_response import ResolveFunctionResponse
from .resource_reference import ResourceReference
from .resource_reference_type import ResourceReferenceType
+from .sandbox_access import SandboxAccess
+from .sandbox_access_request import SandboxAccessRequest
+from .sandbox_capacity import SandboxCapacity
+from .sandbox_capacity_list import SandboxCapacityList
+from .sandbox_command_request import SandboxCommandRequest
+from .sandbox_command_request_environment import SandboxCommandRequestEnvironment
+from .sandbox_command_result import SandboxCommandResult
+from .sandbox_deployment import SandboxDeployment
+from .sandbox_deployment_page import SandboxDeploymentPage
+from .sandbox_execution_result import SandboxExecutionResult
+from .sandbox_file_read_request import SandboxFileReadRequest
+from .sandbox_file_result import SandboxFileResult
+from .sandbox_file_write_request import SandboxFileWriteRequest
+from .sandbox_pagination import SandboxPagination
+from .sandbox_preset import SandboxPreset
+from .sandbox_preset_list import SandboxPresetList
+from .sandbox_preset_memory_mb import SandboxPresetMemoryMb
+from .sandbox_session import SandboxSession
+from .sandbox_session_desired_state import SandboxSessionDesiredState
+from .sandbox_session_page import SandboxSessionPage
+from .sandbox_session_state import SandboxSessionState
+from .sandbox_subject_grant_request import SandboxSubjectGrantRequest
+from .sandbox_template import SandboxTemplate
+from .sandbox_template_page import SandboxTemplatePage
+from .sandbox_template_status import SandboxTemplateStatus
from .schedule_request import ScheduleRequest
from .schedule_request_kind import ScheduleRequestKind
from .service_key import ServiceKey
@@ -501,6 +557,7 @@
from .update_project_git_deploy_settings_request import UpdateProjectGitDeploySettingsRequest
from .update_project_request import UpdateProjectRequest
from .update_realtime_config_request import UpdateRealtimeConfigRequest
+from .update_sandbox_template_request import UpdateSandboxTemplateRequest
from .update_storage_bucket_request import UpdateStorageBucketRequest
from .update_variable_request import UpdateVariableRequest
from .upload_project_logo_body import UploadProjectLogoBody
@@ -611,6 +668,13 @@
"BatchFunctionDeployFailure",
"BatchFunctionDeployFailureOperation",
"BatchFunctionDeployResponse",
+ "CallMCPBody",
+ "CallMCPBodyJsonrpc",
+ "CallMCPBodyParams",
+ "CallMCPResponse200",
+ "CallMCPResponse200Error",
+ "CallMCPResponse200Jsonrpc",
+ "CallMCPResponse200Result",
"CallOAuthProviderAPIBody",
"CallOAuthProviderAPIBodyBody",
"CallOAuthProviderAPIBodyMethod",
@@ -629,6 +693,7 @@
"CreateDatabaseRequestDatabaseType",
"CreateDatabaseRequestPgVersion",
"CreateDatabaseRestoreRequest",
+ "CreatedProjectAccessToken",
"CreateDurableFunctionBody",
"CreateDurableFunctionBodyRuntime",
"CreateDurableFunctionBodyVariableScope",
@@ -636,7 +701,9 @@
"CreateEmailTemplateRequestTemplateType",
"CreateFrontendBody",
"CreateFrontendBodyFramework",
+ "CreateFrontendBodyVariableScope",
"CreateFrontendCustomDomainRequest",
+ "CreateFrontendFunctionRouteRequest",
"CreateFunctionBody",
"CreateFunctionBodyRuntime",
"CreateFunctionBodyVariableScope",
@@ -645,7 +712,11 @@
"CreateFunctionSchedulerRequestPayload",
"CreateOAuthConfigRequest",
"CreateOAuthConfigRequestProvider",
+ "CreateProjectAccessTokenRequest",
"CreateProjectRequest",
+ "CreateSandboxTemplateRequest",
+ "CreateSandboxTemplateRequestMemoryMb",
+ "CreateSandboxTemplateRequestPreset",
"CreateServiceKeyBody",
"CreateStorageBucketRequest",
"CreateStoragePolicyRequest",
@@ -725,10 +796,13 @@
"FrontendDomainRoutingRecordRecordType",
"FrontendDomainVerificationRecord",
"FrontendFramework",
+ "FrontendFunctionRoute",
+ "FrontendFunctionRouteList",
"FrontendStatus",
"FrontendUsageDailyEntry",
"FrontendUsageData",
"FrontendUsageHistoryResponse",
+ "FrontendVariableScope",
"Function",
"FunctionDeployment",
"FunctionDeploymentDeploySource",
@@ -828,6 +902,7 @@
"OAuthConfig",
"OAuthConfigProvider",
"OAuthErrorResponse",
+ "OpenAPISpecDocument",
"PaginatedAuthUsers",
"PaginatedDatabases",
"PaginatedDurableExecutions",
@@ -836,6 +911,7 @@
"PaginatedFrontends",
"PaginatedFunctionDeployments",
"PaginatedFunctions",
+ "PaginatedProjectAccessTokens",
"PaginatedProjectCustomDomains",
"PaginatedProjectDeployments",
"PaginatedProjects",
@@ -847,6 +923,12 @@
"PreviewAuthPageRequest",
"PreviewAuthPageResponse",
"Project",
+ "ProjectAccessToken",
+ "ProjectAccessTokenScope",
+ "ProjectAccessTokenStatus",
+ "ProjectAccessTokenTokenSource",
+ "ProjectAccessTokenUsage",
+ "ProjectAccessTokenUsageDailyEntry",
"ProjectConfig",
"ProjectConfigApplyResult",
"ProjectConfigApplyResultEntry",
@@ -881,6 +963,8 @@
"ProjectConfigEmailTemplate",
"ProjectConfigEmailTemplates",
"ProjectConfigFrontend",
+ "ProjectConfigFrontendFunctionRoute",
+ "ProjectConfigFrontendVariableScope",
"ProjectConfigFunction",
"ProjectConfigFunctionOpenapiSpecType0",
"ProjectConfigFunctionVariableScope",
@@ -964,17 +1048,46 @@
"ProjectSourceExportStateMode",
"ProjectStatus",
"ProjectUsageResponse",
+ "PublishSandboxPresetRequest",
+ "PublishSandboxPresetRequestMemoryMb",
+ "PublishSandboxPresetRequestPreset",
"RealtimeConfig",
"RealtimePlanLimits",
"RealtimeStats",
"RefreshOAuthProviderTokenProvider",
"RefreshOAuthProviderTokenResponse200",
"RenderDefaultManagedAuthPageAction",
+ "ReplaceFrontendSharedVariablesBody",
"ReplaceSharedVariablesBody",
"ResetDatabasePasswordResponse200",
"ResolveFunctionResponse",
"ResourceReference",
"ResourceReferenceType",
+ "SandboxAccess",
+ "SandboxAccessRequest",
+ "SandboxCapacity",
+ "SandboxCapacityList",
+ "SandboxCommandRequest",
+ "SandboxCommandRequestEnvironment",
+ "SandboxCommandResult",
+ "SandboxDeployment",
+ "SandboxDeploymentPage",
+ "SandboxExecutionResult",
+ "SandboxFileReadRequest",
+ "SandboxFileResult",
+ "SandboxFileWriteRequest",
+ "SandboxPagination",
+ "SandboxPreset",
+ "SandboxPresetList",
+ "SandboxPresetMemoryMb",
+ "SandboxSession",
+ "SandboxSessionDesiredState",
+ "SandboxSessionPage",
+ "SandboxSessionState",
+ "SandboxSubjectGrantRequest",
+ "SandboxTemplate",
+ "SandboxTemplatePage",
+ "SandboxTemplateStatus",
"ScheduleRequest",
"ScheduleRequestKind",
"ServiceKey",
@@ -1016,6 +1129,7 @@
"UpdateProjectGitDeploySettingsRequest",
"UpdateProjectRequest",
"UpdateRealtimeConfigRequest",
+ "UpdateSandboxTemplateRequest",
"UpdateStorageBucketRequest",
"UpdateVariableRequest",
"UploadProjectLogoBody",
diff --git a/src/volcano_sdk/_generated/models/call_mcp_body.py b/src/volcano_sdk/_generated/models/call_mcp_body.py
new file mode 100644
index 00000000..1ab23bef
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_body.py
@@ -0,0 +1,135 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.call_mcp_body_jsonrpc import CallMCPBodyJsonrpc
+from ..models.call_mcp_body_jsonrpc import check_call_mcp_body_jsonrpc
+from ..types import UNSET, Unset
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.call_mcp_body_params import CallMCPBodyParams
+
+
+
+
+
+T = TypeVar("T", bound="CallMCPBody")
+
+
+
+@_attrs_define
+class CallMCPBody:
+ """ A JSON-RPC 2.0 request object.
+
+ Attributes:
+ jsonrpc (CallMCPBodyJsonrpc):
+ method (str): The MCP method to call.
+ id (int | str | Unset): Request identifier, echoed verbatim. Omit it to send a
+ notification, which is answered with `202` and no body.
+ params (CallMCPBodyParams | Unset):
+ """
+
+ jsonrpc: CallMCPBodyJsonrpc
+ method: str
+ id: int | str | Unset = UNSET
+ params: CallMCPBodyParams | Unset = UNSET
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.call_mcp_body_params import CallMCPBodyParams
+ jsonrpc: str = self.jsonrpc
+
+ method = self.method
+
+ id: int | str | Unset
+ if isinstance(self.id, Unset):
+ id = UNSET
+ else:
+ id = self.id
+
+ params: dict[str, Any] | Unset = UNSET
+ if not isinstance(self.params, Unset):
+ params = self.params.to_dict()
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "jsonrpc": jsonrpc,
+ "method": method,
+ })
+ if id is not UNSET:
+ field_dict["id"] = id
+ if params is not UNSET:
+ field_dict["params"] = params
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.call_mcp_body_params import CallMCPBodyParams
+ d = dict(src_dict)
+ jsonrpc = check_call_mcp_body_jsonrpc(d.pop("jsonrpc"))
+
+
+
+
+ method = d.pop("method")
+
+ def _parse_id(data: object) -> int | str | Unset:
+ if isinstance(data, Unset):
+ return data
+ return cast(int | str | Unset, data)
+
+ id = _parse_id(d.pop("id", UNSET))
+
+
+ _params = d.pop("params", UNSET)
+ params: CallMCPBodyParams | Unset
+ if isinstance(_params, Unset):
+ params = UNSET
+ else:
+ params = CallMCPBodyParams.from_dict(_params)
+
+
+
+
+ call_mcp_body = cls(
+ jsonrpc=jsonrpc,
+ method=method,
+ id=id,
+ params=params,
+ )
+
+
+ call_mcp_body.additional_properties = d
+ return call_mcp_body
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/call_mcp_body_jsonrpc.py b/src/volcano_sdk/_generated/models/call_mcp_body_jsonrpc.py
new file mode 100644
index 00000000..feece631
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_body_jsonrpc.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+CallMCPBodyJsonrpc = Literal['2.0']
+
+CALL_MCP_BODY_JSONRPC_VALUES: set[CallMCPBodyJsonrpc] = { '2.0', }
+
+def check_call_mcp_body_jsonrpc(value: str) -> CallMCPBodyJsonrpc:
+ if value in CALL_MCP_BODY_JSONRPC_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {CALL_MCP_BODY_JSONRPC_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/call_mcp_body_params.py b/src/volcano_sdk/_generated/models/call_mcp_body_params.py
new file mode 100644
index 00000000..de1de0b4
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_body_params.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="CallMCPBodyParams")
+
+
+
+@_attrs_define
+class CallMCPBodyParams:
+ """
+ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ call_mcp_body_params = cls(
+ )
+
+
+ call_mcp_body_params.additional_properties = d
+ return call_mcp_body_params
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/call_mcp_response_200.py b/src/volcano_sdk/_generated/models/call_mcp_response_200.py
new file mode 100644
index 00000000..84404cd3
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_response_200.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.call_mcp_response_200_jsonrpc import CallMCPResponse200Jsonrpc
+from ..models.call_mcp_response_200_jsonrpc import check_call_mcp_response_200_jsonrpc
+from ..types import UNSET, Unset
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.call_mcp_response_200_error import CallMCPResponse200Error
+ from ..models.call_mcp_response_200_result import CallMCPResponse200Result
+
+
+
+
+
+T = TypeVar("T", bound="CallMCPResponse200")
+
+
+
+@_attrs_define
+class CallMCPResponse200:
+ """
+ Attributes:
+ jsonrpc (CallMCPResponse200Jsonrpc):
+ id (int | None | str): Echoes the request's id. Null when the request could not be read well enough to determine
+ one.
+ result (CallMCPResponse200Result | Unset):
+ error (CallMCPResponse200Error | Unset):
+ """
+
+ jsonrpc: CallMCPResponse200Jsonrpc
+ id: int | None | str
+ result: CallMCPResponse200Result | Unset = UNSET
+ error: CallMCPResponse200Error | Unset = UNSET
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.call_mcp_response_200_error import CallMCPResponse200Error
+ from ..models.call_mcp_response_200_result import CallMCPResponse200Result
+ jsonrpc: str = self.jsonrpc
+
+ id: int | None | str
+ id = self.id
+
+ result: dict[str, Any] | Unset = UNSET
+ if not isinstance(self.result, Unset):
+ result = self.result.to_dict()
+
+ error: dict[str, Any] | Unset = UNSET
+ if not isinstance(self.error, Unset):
+ error = self.error.to_dict()
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "jsonrpc": jsonrpc,
+ "id": id,
+ })
+ if result is not UNSET:
+ field_dict["result"] = result
+ if error is not UNSET:
+ field_dict["error"] = error
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.call_mcp_response_200_error import CallMCPResponse200Error
+ from ..models.call_mcp_response_200_result import CallMCPResponse200Result
+ d = dict(src_dict)
+ jsonrpc = check_call_mcp_response_200_jsonrpc(d.pop("jsonrpc"))
+
+
+
+
+ def _parse_id(data: object) -> int | None | str:
+ if data is None:
+ return data
+ return cast(int | None | str, data)
+
+ id = _parse_id(d.pop("id"))
+
+
+ _result = d.pop("result", UNSET)
+ result: CallMCPResponse200Result | Unset
+ if isinstance(_result, Unset):
+ result = UNSET
+ else:
+ result = CallMCPResponse200Result.from_dict(_result)
+
+
+
+
+ _error = d.pop("error", UNSET)
+ error: CallMCPResponse200Error | Unset
+ if isinstance(_error, Unset):
+ error = UNSET
+ else:
+ error = CallMCPResponse200Error.from_dict(_error)
+
+
+
+
+ call_mcp_response_200 = cls(
+ jsonrpc=jsonrpc,
+ id=id,
+ result=result,
+ error=error,
+ )
+
+
+ call_mcp_response_200.additional_properties = d
+ return call_mcp_response_200
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/call_mcp_response_200_error.py b/src/volcano_sdk/_generated/models/call_mcp_response_200_error.py
new file mode 100644
index 00000000..411aef2f
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_response_200_error.py
@@ -0,0 +1,84 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="CallMCPResponse200Error")
+
+
+
+@_attrs_define
+class CallMCPResponse200Error:
+ """
+ Attributes:
+ code (int):
+ message (str):
+ """
+
+ code: int
+ message: str
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ code = self.code
+
+ message = self.message
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "code": code,
+ "message": message,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ code = d.pop("code")
+
+ message = d.pop("message")
+
+ call_mcp_response_200_error = cls(
+ code=code,
+ message=message,
+ )
+
+
+ call_mcp_response_200_error.additional_properties = d
+ return call_mcp_response_200_error
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/call_mcp_response_200_jsonrpc.py b/src/volcano_sdk/_generated/models/call_mcp_response_200_jsonrpc.py
new file mode 100644
index 00000000..c756422e
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_response_200_jsonrpc.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+CallMCPResponse200Jsonrpc = Literal['2.0']
+
+CALL_MCP_RESPONSE_200_JSONRPC_VALUES: set[CallMCPResponse200Jsonrpc] = { '2.0', }
+
+def check_call_mcp_response_200_jsonrpc(value: str) -> CallMCPResponse200Jsonrpc:
+ if value in CALL_MCP_RESPONSE_200_JSONRPC_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {CALL_MCP_RESPONSE_200_JSONRPC_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/call_mcp_response_200_result.py b/src/volcano_sdk/_generated/models/call_mcp_response_200_result.py
new file mode 100644
index 00000000..df08446d
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/call_mcp_response_200_result.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="CallMCPResponse200Result")
+
+
+
+@_attrs_define
+class CallMCPResponse200Result:
+ """
+ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ call_mcp_response_200_result = cls(
+ )
+
+
+ call_mcp_response_200_result.additional_properties = d
+ return call_mcp_response_200_result
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/create_frontend_body.py b/src/volcano_sdk/_generated/models/create_frontend_body.py
index 28bd1cd6..37d542ac 100644
--- a/src/volcano_sdk/_generated/models/create_frontend_body.py
+++ b/src/volcano_sdk/_generated/models/create_frontend_body.py
@@ -12,6 +12,8 @@
from ..models.create_frontend_body_framework import check_create_frontend_body_framework
from ..models.create_frontend_body_framework import CreateFrontendBodyFramework
+from ..models.create_frontend_body_variable_scope import check_create_frontend_body_variable_scope
+from ..models.create_frontend_body_variable_scope import CreateFrontendBodyVariableScope
from ..types import File, FileTypes
from ..types import UNSET, Unset
from io import BytesIO
@@ -37,12 +39,18 @@ class CreateFrontendBody:
Default: 'nextjs'.
app_root (str | Unset): Optional relative POSIX path from the uploaded archive root to the Next.js app to build,
for example `apps/web`. Example: apps/web.
+ variable_scope (CreateFrontendBodyVariableScope | Unset): Variable selection for this deployment. New frontends
+ default to `scoped`; omitting this field for an existing frontend preserves its current selection.
+ variables (list[str] | Unset): Project variable names selected when `variable_scope` is `scoped`. Submit each
+ name as a repeated multipart field.
"""
name: str
archive: File
framework: CreateFrontendBodyFramework | Unset = 'nextjs'
app_root: str | Unset = UNSET
+ variable_scope: CreateFrontendBodyVariableScope | Unset = UNSET
+ variables: list[str] | Unset = UNSET
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
@@ -62,6 +70,17 @@ def to_dict(self) -> dict[str, Any]:
app_root = self.app_root
+ variable_scope: str | Unset = UNSET
+ if not isinstance(self.variable_scope, Unset):
+ variable_scope = self.variable_scope
+
+
+ variables: list[str] | Unset = UNSET
+ if not isinstance(self.variables, Unset):
+ variables = self.variables
+
+
+
field_dict: dict[str, Any] = {}
field_dict.update(self.additional_properties)
@@ -73,6 +92,10 @@ def to_dict(self) -> dict[str, Any]:
field_dict["framework"] = framework
if app_root is not UNSET:
field_dict["app_root"] = app_root
+ if variable_scope is not UNSET:
+ field_dict["variable_scope"] = variable_scope
+ if variables is not UNSET:
+ field_dict["variables"] = variables
return field_dict
@@ -98,6 +121,18 @@ def to_multipart(self) -> types.RequestFiles:
+ if not isinstance(self.variable_scope, Unset):
+ files.append(("variable_scope", (None, str(self.variable_scope).encode(), "text/plain")))
+
+
+
+ if not isinstance(self.variables, Unset):
+ for variables_item_element in self.variables:
+ files.append(("variables", (None, str(variables_item_element).encode(), "text/plain")))
+
+
+
+
for prop_name, prop in self.additional_properties.items():
files.append((prop_name, (None, str(prop).encode(), "text/plain")))
@@ -131,11 +166,26 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
app_root = d.pop("app_root", UNSET)
+ _variable_scope = d.pop("variable_scope", UNSET)
+ variable_scope: CreateFrontendBodyVariableScope | Unset
+ if isinstance(_variable_scope, Unset):
+ variable_scope = UNSET
+ else:
+ variable_scope = check_create_frontend_body_variable_scope(_variable_scope)
+
+
+
+
+ variables = cast(list[str], d.pop("variables", UNSET))
+
+
create_frontend_body = cls(
name=name,
archive=archive,
framework=framework,
app_root=app_root,
+ variable_scope=variable_scope,
+ variables=variables,
)
diff --git a/src/volcano_sdk/_generated/models/create_frontend_body_variable_scope.py b/src/volcano_sdk/_generated/models/create_frontend_body_variable_scope.py
new file mode 100644
index 00000000..c6663d7d
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/create_frontend_body_variable_scope.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+CreateFrontendBodyVariableScope = Literal['all', 'scoped']
+
+CREATE_FRONTEND_BODY_VARIABLE_SCOPE_VALUES: set[CreateFrontendBodyVariableScope] = { 'all', 'scoped', }
+
+def check_create_frontend_body_variable_scope(value: str) -> CreateFrontendBodyVariableScope:
+ if value in CREATE_FRONTEND_BODY_VARIABLE_SCOPE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {CREATE_FRONTEND_BODY_VARIABLE_SCOPE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/create_frontend_function_route_request.py b/src/volcano_sdk/_generated/models/create_frontend_function_route_request.py
new file mode 100644
index 00000000..30ac34b8
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/create_frontend_function_route_request.py
@@ -0,0 +1,80 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+from uuid import UUID
+
+
+
+
+
+
+T = TypeVar("T", bound="CreateFrontendFunctionRouteRequest")
+
+
+
+@_attrs_define
+class CreateFrontendFunctionRouteRequest:
+ """
+ Attributes:
+ function_id (UUID):
+ path_prefix (str):
+ strip_prefix (bool | Unset): Default: False.
+ """
+
+ function_id: UUID
+ path_prefix: str
+ strip_prefix: bool | Unset = False
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ function_id = str(self.function_id)
+
+ path_prefix = self.path_prefix
+
+ strip_prefix = self.strip_prefix
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "function_id": function_id,
+ "path_prefix": path_prefix,
+ })
+ if strip_prefix is not UNSET:
+ field_dict["strip_prefix"] = strip_prefix
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ function_id = UUID(d.pop("function_id"))
+
+
+
+
+ path_prefix = d.pop("path_prefix")
+
+ strip_prefix = d.pop("strip_prefix", UNSET)
+
+ create_frontend_function_route_request = cls(
+ function_id=function_id,
+ path_prefix=path_prefix,
+ strip_prefix=strip_prefix,
+ )
+
+ return create_frontend_function_route_request
+
diff --git a/src/volcano_sdk/_generated/models/create_project_access_token_request.py b/src/volcano_sdk/_generated/models/create_project_access_token_request.py
new file mode 100644
index 00000000..089930fd
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/create_project_access_token_request.py
@@ -0,0 +1,132 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.project_access_token_scope import check_project_access_token_scope
+from ..models.project_access_token_scope import ProjectAccessTokenScope
+from ..types import UNSET, Unset
+from typing import cast
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="CreateProjectAccessTokenRequest")
+
+
+
+@_attrs_define
+class CreateProjectAccessTokenRequest:
+ """
+ Attributes:
+ name (str): Held by any of the project's tokens you have not revoked, including
+ one that has expired. Creating a duplicate returns 409 with code
+ `access_token_name_exists`; revoking the holder frees the name, so a
+ rotation can keep the name its caller already references.
+ scope (ProjectAccessTokenScope): What a project access token may do within its project.
+
+ `full` is everything you can do to that one project, up to and including
+ deleting it. It cannot manage access tokens, so a leaked token cannot
+ mint a replacement or erase the record of its own use, but for a CI or
+ agent credential that only deploys, prefer `read_only` where the job
+ allows it.
+
+ `read_only` refuses mutations. It is enforced by route classification
+ rather than HTTP method, so the log and metrics query endpoints remain
+ available even though they are POST requests that carry body filters.
+
+ `read_only` also refuses the reads that return a credential — service
+ keys, anon keys, variable values, and database connection strings. Those
+ grant write access over the project's data and keep working after the
+ token that fetched them is revoked, so returning one to a read-only
+ credential would make the scope a formality. An anon key is included
+ because its permissions are chosen per key and may include uploading,
+ deleting, and publishing.
+ expires_at (datetime.datetime | Unset): Omit for a token that does not expire.
+ """
+
+ name: str
+ scope: ProjectAccessTokenScope
+ expires_at: datetime.datetime | Unset = UNSET
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ name = self.name
+
+ scope: str = self.scope
+
+ expires_at: str | Unset = UNSET
+ if not isinstance(self.expires_at, Unset):
+ expires_at = self.expires_at.isoformat()
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "name": name,
+ "scope": scope,
+ })
+ if expires_at is not UNSET:
+ field_dict["expires_at"] = expires_at
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ name = d.pop("name")
+
+ scope = check_project_access_token_scope(d.pop("scope"))
+
+
+
+
+ _expires_at = d.pop("expires_at", UNSET)
+ expires_at: datetime.datetime | Unset
+ if isinstance(_expires_at, Unset):
+ expires_at = UNSET
+ else:
+ expires_at = datetime.datetime.fromisoformat(_expires_at)
+
+
+
+
+ create_project_access_token_request = cls(
+ name=name,
+ scope=scope,
+ expires_at=expires_at,
+ )
+
+
+ create_project_access_token_request.additional_properties = d
+ return create_project_access_token_request
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/create_sandbox_template_request.py b/src/volcano_sdk/_generated/models/create_sandbox_template_request.py
new file mode 100644
index 00000000..e9e47e80
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/create_sandbox_template_request.py
@@ -0,0 +1,95 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.create_sandbox_template_request_memory_mb import check_create_sandbox_template_request_memory_mb
+from ..models.create_sandbox_template_request_memory_mb import CreateSandboxTemplateRequestMemoryMb
+from ..models.create_sandbox_template_request_preset import check_create_sandbox_template_request_preset
+from ..models.create_sandbox_template_request_preset import CreateSandboxTemplateRequestPreset
+from ..types import UNSET, Unset
+from typing import cast
+
+
+
+
+
+
+T = TypeVar("T", bound="CreateSandboxTemplateRequest")
+
+
+
+@_attrs_define
+class CreateSandboxTemplateRequest:
+ """
+ Attributes:
+ name (str):
+ preset (CreateSandboxTemplateRequestPreset):
+ memory_mb (CreateSandboxTemplateRequestMemoryMb | Unset): Default: 1024.
+ """
+
+ name: str
+ preset: CreateSandboxTemplateRequestPreset
+ memory_mb: CreateSandboxTemplateRequestMemoryMb | Unset = 1024
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ name = self.name
+
+ preset: str = self.preset
+
+ memory_mb: int | Unset = UNSET
+ if not isinstance(self.memory_mb, Unset):
+ memory_mb = self.memory_mb
+
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "name": name,
+ "preset": preset,
+ })
+ if memory_mb is not UNSET:
+ field_dict["memory_mb"] = memory_mb
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ name = d.pop("name")
+
+ preset = check_create_sandbox_template_request_preset(d.pop("preset"))
+
+
+
+
+ _memory_mb = d.pop("memory_mb", UNSET)
+ memory_mb: CreateSandboxTemplateRequestMemoryMb | Unset
+ if isinstance(_memory_mb, Unset):
+ memory_mb = UNSET
+ else:
+ memory_mb = check_create_sandbox_template_request_memory_mb(_memory_mb)
+
+
+
+
+ create_sandbox_template_request = cls(
+ name=name,
+ preset=preset,
+ memory_mb=memory_mb,
+ )
+
+ return create_sandbox_template_request
+
diff --git a/src/volcano_sdk/_generated/models/create_sandbox_template_request_memory_mb.py b/src/volcano_sdk/_generated/models/create_sandbox_template_request_memory_mb.py
new file mode 100644
index 00000000..a9b787af
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/create_sandbox_template_request_memory_mb.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+CreateSandboxTemplateRequestMemoryMb = Literal[1024, 2048]
+
+CREATE_SANDBOX_TEMPLATE_REQUEST_MEMORY_MB_VALUES: set[CreateSandboxTemplateRequestMemoryMb] = { 1024, 2048, }
+
+def check_create_sandbox_template_request_memory_mb(value: int) -> CreateSandboxTemplateRequestMemoryMb:
+ if value in CREATE_SANDBOX_TEMPLATE_REQUEST_MEMORY_MB_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {CREATE_SANDBOX_TEMPLATE_REQUEST_MEMORY_MB_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/create_sandbox_template_request_preset.py b/src/volcano_sdk/_generated/models/create_sandbox_template_request_preset.py
new file mode 100644
index 00000000..95352ab0
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/create_sandbox_template_request_preset.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+CreateSandboxTemplateRequestPreset = Literal['node22', 'python3.12']
+
+CREATE_SANDBOX_TEMPLATE_REQUEST_PRESET_VALUES: set[CreateSandboxTemplateRequestPreset] = { 'node22', 'python3.12', }
+
+def check_create_sandbox_template_request_preset(value: str) -> CreateSandboxTemplateRequestPreset:
+ if value in CREATE_SANDBOX_TEMPLATE_REQUEST_PRESET_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {CREATE_SANDBOX_TEMPLATE_REQUEST_PRESET_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/created_project_access_token.py b/src/volcano_sdk/_generated/models/created_project_access_token.py
new file mode 100644
index 00000000..a4c71aec
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/created_project_access_token.py
@@ -0,0 +1,268 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.project_access_token_scope import check_project_access_token_scope
+from ..models.project_access_token_scope import ProjectAccessTokenScope
+from ..models.project_access_token_status import check_project_access_token_status
+from ..models.project_access_token_status import ProjectAccessTokenStatus
+from ..models.project_access_token_token_source import check_project_access_token_token_source
+from ..models.project_access_token_token_source import ProjectAccessTokenTokenSource
+from ..types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="CreatedProjectAccessToken")
+
+
+
+@_attrs_define
+class CreatedProjectAccessToken:
+ """
+ Attributes:
+ id (UUID):
+ project_id (UUID):
+ name (str): Unique per project.
+ token_prefix (str): First 12 characters of the secret, for recognising a token in a list.
+ scope (ProjectAccessTokenScope): What a project access token may do within its project.
+
+ `full` is everything you can do to that one project, up to and including
+ deleting it. It cannot manage access tokens, so a leaked token cannot
+ mint a replacement or erase the record of its own use, but for a CI or
+ agent credential that only deploys, prefer `read_only` where the job
+ allows it.
+
+ `read_only` refuses mutations. It is enforced by route classification
+ rather than HTTP method, so the log and metrics query endpoints remain
+ available even though they are POST requests that carry body filters.
+
+ `read_only` also refuses the reads that return a credential — service
+ keys, anon keys, variable values, and database connection strings. Those
+ grant write access over the project's data and keep working after the
+ token that fetched them is revoked, so returning one to a read-only
+ credential would make the scope a formality. An anon key is included
+ because its permissions are chosen per key and may include uploading,
+ deleting, and publishing.
+ status (ProjectAccessTokenStatus): `revoked` means the token was deliberately revoked, by you or by the
+ deletion of its project. `expired` means it simply reached
+ `expires_at`; nothing was taken away. Both are refused, and both keep
+ their record so a token's name, prefix, last use, and request history
+ remain available after a leak.
+
+ A token revoked before its expiry passed stays `revoked`, because
+ that is the fact worth keeping.
+ token_source (ProjectAccessTokenTokenSource): What created the token.
+ created_at (datetime.datetime):
+ all_time_requests (int): Requests authenticated with this token since it was created.
+ token (str): The secret. Returned only here, and not recoverable afterwards:
+ the server stores a hash rather than the value. Save it now.
+ expires_at (datetime.datetime | None | Unset): Absent for a token that does not expire.
+ last_used_at (datetime.datetime | None | Unset): Updated at most once every few minutes, so it may lag slightly.
+ """
+
+ id: UUID
+ project_id: UUID
+ name: str
+ token_prefix: str
+ scope: ProjectAccessTokenScope
+ status: ProjectAccessTokenStatus
+ token_source: ProjectAccessTokenTokenSource
+ created_at: datetime.datetime
+ all_time_requests: int
+ token: str
+ expires_at: datetime.datetime | None | Unset = UNSET
+ last_used_at: datetime.datetime | None | Unset = UNSET
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ project_id = str(self.project_id)
+
+ name = self.name
+
+ token_prefix = self.token_prefix
+
+ scope: str = self.scope
+
+ status: str = self.status
+
+ token_source: str = self.token_source
+
+ created_at = self.created_at.isoformat()
+
+ all_time_requests = self.all_time_requests
+
+ token = self.token
+
+ expires_at: None | str | Unset
+ if isinstance(self.expires_at, Unset):
+ expires_at = UNSET
+ elif isinstance(self.expires_at, datetime.datetime):
+ expires_at = self.expires_at.isoformat()
+ else:
+ expires_at = self.expires_at
+
+ last_used_at: None | str | Unset
+ if isinstance(self.last_used_at, Unset):
+ last_used_at = UNSET
+ elif isinstance(self.last_used_at, datetime.datetime):
+ last_used_at = self.last_used_at.isoformat()
+ else:
+ last_used_at = self.last_used_at
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "id": id,
+ "project_id": project_id,
+ "name": name,
+ "token_prefix": token_prefix,
+ "scope": scope,
+ "status": status,
+ "token_source": token_source,
+ "created_at": created_at,
+ "all_time_requests": all_time_requests,
+ "token": token,
+ })
+ if expires_at is not UNSET:
+ field_dict["expires_at"] = expires_at
+ if last_used_at is not UNSET:
+ field_dict["last_used_at"] = last_used_at
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ project_id = UUID(d.pop("project_id"))
+
+
+
+
+ name = d.pop("name")
+
+ token_prefix = d.pop("token_prefix")
+
+ scope = check_project_access_token_scope(d.pop("scope"))
+
+
+
+
+ status = check_project_access_token_status(d.pop("status"))
+
+
+
+
+ token_source = check_project_access_token_token_source(d.pop("token_source"))
+
+
+
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+
+
+
+ all_time_requests = d.pop("all_time_requests")
+
+ token = d.pop("token")
+
+ def _parse_expires_at(data: object) -> datetime.datetime | None | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ expires_at_type_0 = datetime.datetime.fromisoformat(data)
+
+
+
+ return expires_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None | Unset, data)
+
+ expires_at = _parse_expires_at(d.pop("expires_at", UNSET))
+
+
+ def _parse_last_used_at(data: object) -> datetime.datetime | None | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ last_used_at_type_0 = datetime.datetime.fromisoformat(data)
+
+
+
+ return last_used_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None | Unset, data)
+
+ last_used_at = _parse_last_used_at(d.pop("last_used_at", UNSET))
+
+
+ created_project_access_token = cls(
+ id=id,
+ project_id=project_id,
+ name=name,
+ token_prefix=token_prefix,
+ scope=scope,
+ status=status,
+ token_source=token_source,
+ created_at=created_at,
+ all_time_requests=all_time_requests,
+ token=token,
+ expires_at=expires_at,
+ last_used_at=last_used_at,
+ )
+
+
+ created_project_access_token.additional_properties = d
+ return created_project_access_token
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/durable_execution.py b/src/volcano_sdk/_generated/models/durable_execution.py
index 3f4862ac..b1fc9377 100644
--- a/src/volcano_sdk/_generated/models/durable_execution.py
+++ b/src/volcano_sdk/_generated/models/durable_execution.py
@@ -40,12 +40,18 @@ class DurableExecution:
`succeeded`, `failed`, `timed_out`, `stopped` and `unknown` are
terminal.
- `unknown` means the platform lost track of the execution's outcome: it
- was never seen to finish and is no longer reported, so no result or
- error can be given for it. It is terminal because nothing can settle it
- later, and it is rare — treat it as an outcome to retry under a new
- name rather than a state to wait on. `completed_at` on an `unknown`
- execution is when the platform gave up, not when the work ended.
+ `unknown` means the execution's outcome cannot be established, so no
+ result or error can be given for it. Either it was under way and was
+ never seen to finish, or its start failed with a `500` without the
+ platform establishing whether the execution began — which is why a
+ name whose start returned an error can later read as `unknown` rather
+ than not being found. It is terminal because nothing can settle it
+ later, and it is rare — treat it as an outcome to retry rather than a
+ state to wait on. A retry under the same name picks this execution back
+ up instead of starting a second one, and needs a free concurrency slot
+ because an `unknown` execution has given its own up. `completed_at` on
+ an `unknown` execution is when the platform gave up, not when the work
+ ended.
region (str): Region the execution runs in. An execution is pinned to one region
for its whole life because its checkpoints live there.
created_at (datetime.datetime):
diff --git a/src/volcano_sdk/_generated/models/frontend.py b/src/volcano_sdk/_generated/models/frontend.py
index 061ec4fc..3081a520 100644
--- a/src/volcano_sdk/_generated/models/frontend.py
+++ b/src/volcano_sdk/_generated/models/frontend.py
@@ -14,6 +14,8 @@
from ..models.frontend_framework import FrontendFramework
from ..models.frontend_status import check_frontend_status
from ..models.frontend_status import FrontendStatus
+from ..models.frontend_variable_scope import check_frontend_variable_scope
+from ..models.frontend_variable_scope import FrontendVariableScope
from ..types import UNSET, Unset
from typing import cast
from uuid import UUID
@@ -46,6 +48,11 @@ class Frontend:
deployed_regions (list[str]):
created_at (datetime.datetime):
updated_at (datetime.datetime):
+ variable_scope (FrontendVariableScope | Unset): All preserves access to all project variables. Shared includes
+ the project frontend shared-variable list. Scoped includes only explicitly declared variables in builds and
+ runtime. Omission preserves the stored selection.
+ declared_variables (list[str] | Unset): Names selected when variable_scope is scoped. Missing declared values
+ reject deployment. Omission preserves the stored list; an empty list clears it.
app_root (str | Unset): Optional relative POSIX path from the uploaded archive root to the Next.js app that is
deployed.
provisioning_started_at (datetime.datetime | Unset): Timestamp when the current provisioning phase started
@@ -65,6 +72,8 @@ class Frontend:
deployed_regions: list[str]
created_at: datetime.datetime
updated_at: datetime.datetime
+ variable_scope: FrontendVariableScope | Unset = UNSET
+ declared_variables: list[str] | Unset = UNSET
app_root: str | Unset = UNSET
provisioning_started_at: datetime.datetime | Unset = UNSET
current_deployment_id: UUID | Unset = UNSET
@@ -98,6 +107,17 @@ def to_dict(self) -> dict[str, Any]:
updated_at = self.updated_at.isoformat()
+ variable_scope: str | Unset = UNSET
+ if not isinstance(self.variable_scope, Unset):
+ variable_scope = self.variable_scope
+
+
+ declared_variables: list[str] | Unset = UNSET
+ if not isinstance(self.declared_variables, Unset):
+ declared_variables = self.declared_variables
+
+
+
app_root = self.app_root
provisioning_started_at: str | Unset = UNSET
@@ -138,6 +158,10 @@ def to_dict(self) -> dict[str, Any]:
"created_at": created_at,
"updated_at": updated_at,
})
+ if variable_scope is not UNSET:
+ field_dict["variable_scope"] = variable_scope
+ if declared_variables is not UNSET:
+ field_dict["declared_variables"] = declared_variables
if app_root is not UNSET:
field_dict["app_root"] = app_root
if provisioning_started_at is not UNSET:
@@ -197,6 +221,19 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ _variable_scope = d.pop("variable_scope", UNSET)
+ variable_scope: FrontendVariableScope | Unset
+ if isinstance(_variable_scope, Unset):
+ variable_scope = UNSET
+ else:
+ variable_scope = check_frontend_variable_scope(_variable_scope)
+
+
+
+
+ declared_variables = cast(list[str], d.pop("declared_variables", UNSET))
+
+
app_root = d.pop("app_root", UNSET)
_provisioning_started_at = d.pop("provisioning_started_at", UNSET)
@@ -262,6 +299,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
deployed_regions=deployed_regions,
created_at=created_at,
updated_at=updated_at,
+ variable_scope=variable_scope,
+ declared_variables=declared_variables,
app_root=app_root,
provisioning_started_at=provisioning_started_at,
current_deployment_id=current_deployment_id,
diff --git a/src/volcano_sdk/_generated/models/frontend_function_route.py b/src/volcano_sdk/_generated/models/frontend_function_route.py
new file mode 100644
index 00000000..3d50d048
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/frontend_function_route.py
@@ -0,0 +1,135 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+from uuid import UUID
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="FrontendFunctionRoute")
+
+
+
+@_attrs_define
+class FrontendFunctionRoute:
+ """
+ Attributes:
+ id (UUID):
+ project_id (UUID):
+ frontend_id (UUID):
+ function_id (UUID):
+ path_prefix (str):
+ strip_prefix (bool):
+ created_at (datetime.datetime):
+ updated_at (datetime.datetime):
+ """
+
+ id: UUID
+ project_id: UUID
+ frontend_id: UUID
+ function_id: UUID
+ path_prefix: str
+ strip_prefix: bool
+ created_at: datetime.datetime
+ updated_at: datetime.datetime
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ project_id = str(self.project_id)
+
+ frontend_id = str(self.frontend_id)
+
+ function_id = str(self.function_id)
+
+ path_prefix = self.path_prefix
+
+ strip_prefix = self.strip_prefix
+
+ created_at = self.created_at.isoformat()
+
+ updated_at = self.updated_at.isoformat()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "id": id,
+ "project_id": project_id,
+ "frontend_id": frontend_id,
+ "function_id": function_id,
+ "path_prefix": path_prefix,
+ "strip_prefix": strip_prefix,
+ "created_at": created_at,
+ "updated_at": updated_at,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ project_id = UUID(d.pop("project_id"))
+
+
+
+
+ frontend_id = UUID(d.pop("frontend_id"))
+
+
+
+
+ function_id = UUID(d.pop("function_id"))
+
+
+
+
+ path_prefix = d.pop("path_prefix")
+
+ strip_prefix = d.pop("strip_prefix")
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+
+
+
+ updated_at = datetime.datetime.fromisoformat(d.pop("updated_at"))
+
+
+
+
+ frontend_function_route = cls(
+ id=id,
+ project_id=project_id,
+ frontend_id=frontend_id,
+ function_id=function_id,
+ path_prefix=path_prefix,
+ strip_prefix=strip_prefix,
+ created_at=created_at,
+ updated_at=updated_at,
+ )
+
+ return frontend_function_route
+
diff --git a/src/volcano_sdk/_generated/models/frontend_function_route_list.py b/src/volcano_sdk/_generated/models/frontend_function_route_list.py
new file mode 100644
index 00000000..293907e8
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/frontend_function_route_list.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.frontend_function_route import FrontendFunctionRoute
+
+
+
+
+
+T = TypeVar("T", bound="FrontendFunctionRouteList")
+
+
+
+@_attrs_define
+class FrontendFunctionRouteList:
+ """
+ Attributes:
+ data (list[FrontendFunctionRoute]):
+ """
+
+ data: list[FrontendFunctionRoute]
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.frontend_function_route import FrontendFunctionRoute
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.frontend_function_route import FrontendFunctionRoute
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = FrontendFunctionRoute.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ frontend_function_route_list = cls(
+ data=data,
+ )
+
+ return frontend_function_route_list
+
diff --git a/src/volcano_sdk/_generated/models/frontend_variable_scope.py b/src/volcano_sdk/_generated/models/frontend_variable_scope.py
new file mode 100644
index 00000000..322d1b60
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/frontend_variable_scope.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+FrontendVariableScope = Literal['all', 'scoped', 'shared']
+
+FRONTEND_VARIABLE_SCOPE_VALUES: set[FrontendVariableScope] = { 'all', 'scoped', 'shared', }
+
+def check_frontend_variable_scope(value: str) -> FrontendVariableScope:
+ if value in FRONTEND_VARIABLE_SCOPE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {FRONTEND_VARIABLE_SCOPE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/function.py b/src/volcano_sdk/_generated/models/function.py
index fcbd8098..288f9779 100644
--- a/src/volcano_sdk/_generated/models/function.py
+++ b/src/volcano_sdk/_generated/models/function.py
@@ -55,7 +55,10 @@ class Function:
updated_at (datetime.datetime):
provisioning_started_at (datetime.datetime | Unset): Timestamp when the current provisioning phase started
aws_function_arn (str | Unset):
- invoke_url (str | Unset): Canonical GeoDNS endpoint URL for invoking this function (always HTTPS)
+ invoke_url (str | Unset): Canonical geo-routed HTTPS endpoint for invoking this function. Use it as-is: it does
+ not share a domain with the API, so a host derived from the API URL will not reach the function. Omitted when
+ the deployment serves no public invocation domain, as in local development, so a client testing for an empty
+ string never matches.
runtime (str | Unset):
handler (str | Unset):
current_deployment_id (UUID | Unset): Identifier of the latest function deployment operation
diff --git a/src/volcano_sdk/_generated/models/open_api_spec_document.py b/src/volcano_sdk/_generated/models/open_api_spec_document.py
new file mode 100644
index 00000000..0071cf4f
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/open_api_spec_document.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="OpenAPISpecDocument")
+
+
+
+@_attrs_define
+class OpenAPISpecDocument:
+ """ This OpenAPI document, with every reference resolved. Shared by the JSON
+ and YAML operations, which differ only in serialization.
+
+ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ open_api_spec_document = cls(
+ )
+
+
+ open_api_spec_document.additional_properties = d
+ return open_api_spec_document
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/paginated_project_access_tokens.py b/src/volcano_sdk/_generated/models/paginated_project_access_tokens.py
new file mode 100644
index 00000000..60b7389f
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/paginated_project_access_tokens.py
@@ -0,0 +1,136 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.project_access_token import ProjectAccessToken
+
+
+
+
+
+T = TypeVar("T", bound="PaginatedProjectAccessTokens")
+
+
+
+@_attrs_define
+class PaginatedProjectAccessTokens:
+ """
+ Attributes:
+ data (list[ProjectAccessToken]):
+ page (int):
+ limit (int):
+ total (int):
+ has_more (bool):
+ next_ (str | Unset):
+ """
+
+ data: list[ProjectAccessToken]
+ page: int
+ limit: int
+ total: int
+ has_more: bool
+ next_: str | Unset = UNSET
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.project_access_token import ProjectAccessToken
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+ page = self.page
+
+ limit = self.limit
+
+ total = self.total
+
+ has_more = self.has_more
+
+ next_ = self.next_
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "data": data,
+ "page": page,
+ "limit": limit,
+ "total": total,
+ "has_more": has_more,
+ })
+ if next_ is not UNSET:
+ field_dict["next"] = next_
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.project_access_token import ProjectAccessToken
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = ProjectAccessToken.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ page = d.pop("page")
+
+ limit = d.pop("limit")
+
+ total = d.pop("total")
+
+ has_more = d.pop("has_more")
+
+ next_ = d.pop("next", UNSET)
+
+ paginated_project_access_tokens = cls(
+ data=data,
+ page=page,
+ limit=limit,
+ total=total,
+ has_more=has_more,
+ next_=next_,
+ )
+
+
+ paginated_project_access_tokens.additional_properties = d
+ return paginated_project_access_tokens
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/project_access_token.py b/src/volcano_sdk/_generated/models/project_access_token.py
new file mode 100644
index 00000000..e7e81f8f
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_access_token.py
@@ -0,0 +1,265 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.project_access_token_scope import check_project_access_token_scope
+from ..models.project_access_token_scope import ProjectAccessTokenScope
+from ..models.project_access_token_status import check_project_access_token_status
+from ..models.project_access_token_status import ProjectAccessTokenStatus
+from ..models.project_access_token_token_source import check_project_access_token_token_source
+from ..models.project_access_token_token_source import ProjectAccessTokenTokenSource
+from ..types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="ProjectAccessToken")
+
+
+
+@_attrs_define
+class ProjectAccessToken:
+ """ A project access token: a control-plane credential bound to a single
+ project. Unlike a platform token, which acts on every project its owner
+ has, this one is limited to the project it was created in.
+
+ The secret itself is never returned here. Only its hash is stored, so
+ the plaintext exists solely in the response to the create call.
+
+ Attributes:
+ id (UUID):
+ project_id (UUID):
+ name (str): Unique per project.
+ token_prefix (str): First 12 characters of the secret, for recognising a token in a list.
+ scope (ProjectAccessTokenScope): What a project access token may do within its project.
+
+ `full` is everything you can do to that one project, up to and including
+ deleting it. It cannot manage access tokens, so a leaked token cannot
+ mint a replacement or erase the record of its own use, but for a CI or
+ agent credential that only deploys, prefer `read_only` where the job
+ allows it.
+
+ `read_only` refuses mutations. It is enforced by route classification
+ rather than HTTP method, so the log and metrics query endpoints remain
+ available even though they are POST requests that carry body filters.
+
+ `read_only` also refuses the reads that return a credential — service
+ keys, anon keys, variable values, and database connection strings. Those
+ grant write access over the project's data and keep working after the
+ token that fetched them is revoked, so returning one to a read-only
+ credential would make the scope a formality. An anon key is included
+ because its permissions are chosen per key and may include uploading,
+ deleting, and publishing.
+ status (ProjectAccessTokenStatus): `revoked` means the token was deliberately revoked, by you or by the
+ deletion of its project. `expired` means it simply reached
+ `expires_at`; nothing was taken away. Both are refused, and both keep
+ their record so a token's name, prefix, last use, and request history
+ remain available after a leak.
+
+ A token revoked before its expiry passed stays `revoked`, because
+ that is the fact worth keeping.
+ token_source (ProjectAccessTokenTokenSource): What created the token.
+ created_at (datetime.datetime):
+ all_time_requests (int): Requests authenticated with this token since it was created.
+ expires_at (datetime.datetime | None | Unset): Absent for a token that does not expire.
+ last_used_at (datetime.datetime | None | Unset): Updated at most once every few minutes, so it may lag slightly.
+ """
+
+ id: UUID
+ project_id: UUID
+ name: str
+ token_prefix: str
+ scope: ProjectAccessTokenScope
+ status: ProjectAccessTokenStatus
+ token_source: ProjectAccessTokenTokenSource
+ created_at: datetime.datetime
+ all_time_requests: int
+ expires_at: datetime.datetime | None | Unset = UNSET
+ last_used_at: datetime.datetime | None | Unset = UNSET
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ project_id = str(self.project_id)
+
+ name = self.name
+
+ token_prefix = self.token_prefix
+
+ scope: str = self.scope
+
+ status: str = self.status
+
+ token_source: str = self.token_source
+
+ created_at = self.created_at.isoformat()
+
+ all_time_requests = self.all_time_requests
+
+ expires_at: None | str | Unset
+ if isinstance(self.expires_at, Unset):
+ expires_at = UNSET
+ elif isinstance(self.expires_at, datetime.datetime):
+ expires_at = self.expires_at.isoformat()
+ else:
+ expires_at = self.expires_at
+
+ last_used_at: None | str | Unset
+ if isinstance(self.last_used_at, Unset):
+ last_used_at = UNSET
+ elif isinstance(self.last_used_at, datetime.datetime):
+ last_used_at = self.last_used_at.isoformat()
+ else:
+ last_used_at = self.last_used_at
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "id": id,
+ "project_id": project_id,
+ "name": name,
+ "token_prefix": token_prefix,
+ "scope": scope,
+ "status": status,
+ "token_source": token_source,
+ "created_at": created_at,
+ "all_time_requests": all_time_requests,
+ })
+ if expires_at is not UNSET:
+ field_dict["expires_at"] = expires_at
+ if last_used_at is not UNSET:
+ field_dict["last_used_at"] = last_used_at
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ project_id = UUID(d.pop("project_id"))
+
+
+
+
+ name = d.pop("name")
+
+ token_prefix = d.pop("token_prefix")
+
+ scope = check_project_access_token_scope(d.pop("scope"))
+
+
+
+
+ status = check_project_access_token_status(d.pop("status"))
+
+
+
+
+ token_source = check_project_access_token_token_source(d.pop("token_source"))
+
+
+
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+
+
+
+ all_time_requests = d.pop("all_time_requests")
+
+ def _parse_expires_at(data: object) -> datetime.datetime | None | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ expires_at_type_0 = datetime.datetime.fromisoformat(data)
+
+
+
+ return expires_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None | Unset, data)
+
+ expires_at = _parse_expires_at(d.pop("expires_at", UNSET))
+
+
+ def _parse_last_used_at(data: object) -> datetime.datetime | None | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ last_used_at_type_0 = datetime.datetime.fromisoformat(data)
+
+
+
+ return last_used_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None | Unset, data)
+
+ last_used_at = _parse_last_used_at(d.pop("last_used_at", UNSET))
+
+
+ project_access_token = cls(
+ id=id,
+ project_id=project_id,
+ name=name,
+ token_prefix=token_prefix,
+ scope=scope,
+ status=status,
+ token_source=token_source,
+ created_at=created_at,
+ all_time_requests=all_time_requests,
+ expires_at=expires_at,
+ last_used_at=last_used_at,
+ )
+
+
+ project_access_token.additional_properties = d
+ return project_access_token
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/project_access_token_scope.py b/src/volcano_sdk/_generated/models/project_access_token_scope.py
new file mode 100644
index 00000000..f812fed6
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_access_token_scope.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+ProjectAccessTokenScope = Literal['full', 'read_only']
+
+PROJECT_ACCESS_TOKEN_SCOPE_VALUES: set[ProjectAccessTokenScope] = { 'full', 'read_only', }
+
+def check_project_access_token_scope(value: str) -> ProjectAccessTokenScope:
+ if value in PROJECT_ACCESS_TOKEN_SCOPE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {PROJECT_ACCESS_TOKEN_SCOPE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/project_access_token_status.py b/src/volcano_sdk/_generated/models/project_access_token_status.py
new file mode 100644
index 00000000..077c7be8
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_access_token_status.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+ProjectAccessTokenStatus = Literal['active', 'expired', 'revoked']
+
+PROJECT_ACCESS_TOKEN_STATUS_VALUES: set[ProjectAccessTokenStatus] = { 'active', 'expired', 'revoked', }
+
+def check_project_access_token_status(value: str) -> ProjectAccessTokenStatus:
+ if value in PROJECT_ACCESS_TOKEN_STATUS_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {PROJECT_ACCESS_TOKEN_STATUS_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/project_access_token_token_source.py b/src/volcano_sdk/_generated/models/project_access_token_token_source.py
new file mode 100644
index 00000000..e82dc2e7
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_access_token_token_source.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+ProjectAccessTokenTokenSource = Literal['api', 'cli', 'dashboard']
+
+PROJECT_ACCESS_TOKEN_TOKEN_SOURCE_VALUES: set[ProjectAccessTokenTokenSource] = { 'api', 'cli', 'dashboard', }
+
+def check_project_access_token_token_source(value: str) -> ProjectAccessTokenTokenSource:
+ if value in PROJECT_ACCESS_TOKEN_TOKEN_SOURCE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {PROJECT_ACCESS_TOKEN_TOKEN_SOURCE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/project_access_token_usage.py b/src/volcano_sdk/_generated/models/project_access_token_usage.py
new file mode 100644
index 00000000..75587208
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_access_token_usage.py
@@ -0,0 +1,149 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+from uuid import UUID
+
+if TYPE_CHECKING:
+ from ..models.project_access_token_usage_daily_entry import ProjectAccessTokenUsageDailyEntry
+
+
+
+
+
+T = TypeVar("T", bound="ProjectAccessTokenUsage")
+
+
+
+@_attrs_define
+class ProjectAccessTokenUsage:
+ """ Zero-filled daily request counts for a single token, oldest first. Every
+ day in the window is present, so a gap reads as zero rather than missing.
+
+ Counts every request the token authenticated, including ones then
+ refused — a read-only token attempting a write, or a token presented on
+ another project's route. That is deliberate: after a leak, the probing
+ is the part you want to see, and a counter that hid it would make a
+ token look idle while it was being tried.
+
+ Attributes:
+ token_id (UUID):
+ name (str):
+ token_prefix (str): The token's display prefix, which identifies the credential when its
+ name does not. Revoking frees a name, so a project that rotated
+ `ci-deploy` has two entries here both called `ci-deploy`. Not usable
+ as a credential.
+ days (int): Number of daily entries returned, always equal to the requested window.
+ daily (list[ProjectAccessTokenUsageDailyEntry]):
+ total_requests (int):
+ """
+
+ token_id: UUID
+ name: str
+ token_prefix: str
+ days: int
+ daily: list[ProjectAccessTokenUsageDailyEntry]
+ total_requests: int
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.project_access_token_usage_daily_entry import ProjectAccessTokenUsageDailyEntry
+ token_id = str(self.token_id)
+
+ name = self.name
+
+ token_prefix = self.token_prefix
+
+ days = self.days
+
+ daily = []
+ for daily_item_data in self.daily:
+ daily_item = daily_item_data.to_dict()
+ daily.append(daily_item)
+
+
+
+ total_requests = self.total_requests
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "token_id": token_id,
+ "name": name,
+ "token_prefix": token_prefix,
+ "days": days,
+ "daily": daily,
+ "total_requests": total_requests,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.project_access_token_usage_daily_entry import ProjectAccessTokenUsageDailyEntry
+ d = dict(src_dict)
+ token_id = UUID(d.pop("token_id"))
+
+
+
+
+ name = d.pop("name")
+
+ token_prefix = d.pop("token_prefix")
+
+ days = d.pop("days")
+
+ daily = []
+ _daily = d.pop("daily")
+ for daily_item_data in (_daily):
+ daily_item = ProjectAccessTokenUsageDailyEntry.from_dict(daily_item_data)
+
+
+
+ daily.append(daily_item)
+
+
+ total_requests = d.pop("total_requests")
+
+ project_access_token_usage = cls(
+ token_id=token_id,
+ name=name,
+ token_prefix=token_prefix,
+ days=days,
+ daily=daily,
+ total_requests=total_requests,
+ )
+
+
+ project_access_token_usage.additional_properties = d
+ return project_access_token_usage
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/project_access_token_usage_daily_entry.py b/src/volcano_sdk/_generated/models/project_access_token_usage_daily_entry.py
new file mode 100644
index 00000000..f89ab22c
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_access_token_usage_daily_entry.py
@@ -0,0 +1,89 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="ProjectAccessTokenUsageDailyEntry")
+
+
+
+@_attrs_define
+class ProjectAccessTokenUsageDailyEntry:
+ """
+ Attributes:
+ day (datetime.date): UTC day.
+ requests (int):
+ """
+
+ day: datetime.date
+ requests: int
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ day = self.day.isoformat()
+
+ requests = self.requests
+
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+ field_dict.update({
+ "day": day,
+ "requests": requests,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ day = datetime.date.fromisoformat(d.pop("day"))
+
+
+
+
+ requests = d.pop("requests")
+
+ project_access_token_usage_daily_entry = cls(
+ day=day,
+ requests=requests,
+ )
+
+
+ project_access_token_usage_daily_entry.additional_properties = d
+ return project_access_token_usage_daily_entry
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/project_config.py b/src/volcano_sdk/_generated/models/project_config.py
index 9e571c93..e9d089ef 100644
--- a/src/volcano_sdk/_generated/models/project_config.py
+++ b/src/volcano_sdk/_generated/models/project_config.py
@@ -50,6 +50,9 @@ class ProjectConfig:
databases (list[ProjectConfigDatabase] | Unset):
shared_variables (list[str] | Unset): Replace the complete shared function-variable list with existing names,
without changing variable values. Omission keeps membership unchanged; an empty list clears it.
+ frontend_shared_variables (list[str] | Unset): Replace the complete shared frontend-variable list with existing
+ names. Frontends with variable_scope shared receive this list. Omission keeps membership unchanged; an empty
+ list clears it.
variables (list[ProjectConfigVariable] | Unset): Fully synced when declared - variables absent from this list
are deleted.
buckets (list[ProjectConfigBucket] | Unset):
@@ -63,6 +66,7 @@ class ProjectConfig:
project: ProjectConfigProject | Unset = UNSET
databases: list[ProjectConfigDatabase] | Unset = UNSET
shared_variables: list[str] | Unset = UNSET
+ frontend_shared_variables: list[str] | Unset = UNSET
variables: list[ProjectConfigVariable] | Unset = UNSET
buckets: list[ProjectConfigBucket] | Unset = UNSET
realtime: ProjectConfigRealtime | Unset = UNSET
@@ -104,6 +108,12 @@ def to_dict(self) -> dict[str, Any]:
+ frontend_shared_variables: list[str] | Unset = UNSET
+ if not isinstance(self.frontend_shared_variables, Unset):
+ frontend_shared_variables = self.frontend_shared_variables
+
+
+
variables: list[dict[str, Any]] | Unset = UNSET
if not isinstance(self.variables, Unset):
variables = []
@@ -160,6 +170,8 @@ def to_dict(self) -> dict[str, Any]:
field_dict["databases"] = databases
if shared_variables is not UNSET:
field_dict["shared_variables"] = shared_variables
+ if frontend_shared_variables is not UNSET:
+ field_dict["frontend_shared_variables"] = frontend_shared_variables
if variables is not UNSET:
field_dict["variables"] = variables
if buckets is not UNSET:
@@ -218,6 +230,9 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
shared_variables = cast(list[str], d.pop("shared_variables", UNSET))
+ frontend_shared_variables = cast(list[str], d.pop("frontend_shared_variables", UNSET))
+
+
_variables = d.pop("variables", UNSET)
variables: list[ProjectConfigVariable] | Unset = UNSET
if _variables is not UNSET:
@@ -291,6 +306,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
project=project,
databases=databases,
shared_variables=shared_variables,
+ frontend_shared_variables=frontend_shared_variables,
variables=variables,
buckets=buckets,
realtime=realtime,
diff --git a/src/volcano_sdk/_generated/models/project_config_frontend.py b/src/volcano_sdk/_generated/models/project_config_frontend.py
index f4ae1e83..6a48c3ee 100644
--- a/src/volcano_sdk/_generated/models/project_config_frontend.py
+++ b/src/volcano_sdk/_generated/models/project_config_frontend.py
@@ -8,11 +8,14 @@
from ..types import UNSET, Unset
+from ..models.project_config_frontend_variable_scope import check_project_config_frontend_variable_scope
+from ..models.project_config_frontend_variable_scope import ProjectConfigFrontendVariableScope
from ..types import UNSET, Unset
from typing import cast
if TYPE_CHECKING:
from ..models.project_config_custom_domain import ProjectConfigCustomDomain
+ from ..models.project_config_frontend_function_route import ProjectConfigFrontendFunctionRoute
@@ -30,16 +33,26 @@ class ProjectConfigFrontend:
Attributes:
name (str):
+ variable_scope (ProjectConfigFrontendVariableScope | Unset): All preserves access to all project variables.
+ Shared includes the project frontend_shared_variables list. Scoped includes only explicitly declared variables
+ in builds and runtime. Omission preserves the stored selection.
+ variables (list[str] | Unset): Names selected when variable_scope is scoped. Missing declared values reject
+ deployment. Omission preserves the stored list; an empty list clears it.
custom_domain (ProjectConfigCustomDomain | Unset): Custom domain with BYOC TLS (PRO plan). `tls` is required
when the
domain is first created and optional afterwards: providing new TLS
material for the same domain rotates the certificate in place (zero
downtime); omitting `tls` keeps the stored certificate. TLS material is
write-only and omitted from config export.
+ function_routes (list[ProjectConfigFrontendFunctionRoute] | Unset): Complete set of same-origin Function path
+ mappings when declared. Omission preserves existing mappings; an empty list deletes all mappings.
"""
name: str
+ variable_scope: ProjectConfigFrontendVariableScope | Unset = UNSET
+ variables: list[str] | Unset = UNSET
custom_domain: ProjectConfigCustomDomain | Unset = UNSET
+ function_routes: list[ProjectConfigFrontendFunctionRoute] | Unset = UNSET
@@ -47,20 +60,47 @@ class ProjectConfigFrontend:
def to_dict(self) -> dict[str, Any]:
from ..models.project_config_custom_domain import ProjectConfigCustomDomain
+ from ..models.project_config_frontend_function_route import ProjectConfigFrontendFunctionRoute
name = self.name
+ variable_scope: str | Unset = UNSET
+ if not isinstance(self.variable_scope, Unset):
+ variable_scope = self.variable_scope
+
+
+ variables: list[str] | Unset = UNSET
+ if not isinstance(self.variables, Unset):
+ variables = self.variables
+
+
+
custom_domain: dict[str, Any] | Unset = UNSET
if not isinstance(self.custom_domain, Unset):
custom_domain = self.custom_domain.to_dict()
+ function_routes: list[dict[str, Any]] | Unset = UNSET
+ if not isinstance(self.function_routes, Unset):
+ function_routes = []
+ for function_routes_item_data in self.function_routes:
+ function_routes_item = function_routes_item_data.to_dict()
+ function_routes.append(function_routes_item)
+
+
+
field_dict: dict[str, Any] = {}
field_dict.update({
"name": name,
})
+ if variable_scope is not UNSET:
+ field_dict["variable_scope"] = variable_scope
+ if variables is not UNSET:
+ field_dict["variables"] = variables
if custom_domain is not UNSET:
field_dict["custom_domain"] = custom_domain
+ if function_routes is not UNSET:
+ field_dict["function_routes"] = function_routes
return field_dict
@@ -69,9 +109,23 @@ def to_dict(self) -> dict[str, Any]:
@classmethod
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
from ..models.project_config_custom_domain import ProjectConfigCustomDomain
+ from ..models.project_config_frontend_function_route import ProjectConfigFrontendFunctionRoute
d = dict(src_dict)
name = d.pop("name")
+ _variable_scope = d.pop("variable_scope", UNSET)
+ variable_scope: ProjectConfigFrontendVariableScope | Unset
+ if isinstance(_variable_scope, Unset):
+ variable_scope = UNSET
+ else:
+ variable_scope = check_project_config_frontend_variable_scope(_variable_scope)
+
+
+
+
+ variables = cast(list[str], d.pop("variables", UNSET))
+
+
_custom_domain = d.pop("custom_domain", UNSET)
custom_domain: ProjectConfigCustomDomain | Unset
if isinstance(_custom_domain, Unset):
@@ -82,9 +136,24 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ _function_routes = d.pop("function_routes", UNSET)
+ function_routes: list[ProjectConfigFrontendFunctionRoute] | Unset = UNSET
+ if _function_routes is not UNSET:
+ function_routes = []
+ for function_routes_item_data in _function_routes:
+ function_routes_item = ProjectConfigFrontendFunctionRoute.from_dict(function_routes_item_data)
+
+
+
+ function_routes.append(function_routes_item)
+
+
project_config_frontend = cls(
name=name,
+ variable_scope=variable_scope,
+ variables=variables,
custom_domain=custom_domain,
+ function_routes=function_routes,
)
return project_config_frontend
diff --git a/src/volcano_sdk/_generated/models/project_config_frontend_function_route.py b/src/volcano_sdk/_generated/models/project_config_frontend_function_route.py
new file mode 100644
index 00000000..e627a611
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_config_frontend_function_route.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+T = TypeVar("T", bound="ProjectConfigFrontendFunctionRoute")
+
+
+
+@_attrs_define
+class ProjectConfigFrontendFunctionRoute:
+ """
+ Attributes:
+ function (str): Name of an existing standard Function configured for HTTP invocation.
+ path_prefix (str):
+ strip_prefix (bool | Unset): Default: False.
+ """
+
+ function: str
+ path_prefix: str
+ strip_prefix: bool | Unset = False
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ function = self.function
+
+ path_prefix = self.path_prefix
+
+ strip_prefix = self.strip_prefix
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "function": function,
+ "path_prefix": path_prefix,
+ })
+ if strip_prefix is not UNSET:
+ field_dict["strip_prefix"] = strip_prefix
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ function = d.pop("function")
+
+ path_prefix = d.pop("path_prefix")
+
+ strip_prefix = d.pop("strip_prefix", UNSET)
+
+ project_config_frontend_function_route = cls(
+ function=function,
+ path_prefix=path_prefix,
+ strip_prefix=strip_prefix,
+ )
+
+ return project_config_frontend_function_route
+
diff --git a/src/volcano_sdk/_generated/models/project_config_frontend_variable_scope.py b/src/volcano_sdk/_generated/models/project_config_frontend_variable_scope.py
new file mode 100644
index 00000000..c66bee87
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/project_config_frontend_variable_scope.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+ProjectConfigFrontendVariableScope = Literal['all', 'scoped', 'shared']
+
+PROJECT_CONFIG_FRONTEND_VARIABLE_SCOPE_VALUES: set[ProjectConfigFrontendVariableScope] = { 'all', 'scoped', 'shared', }
+
+def check_project_config_frontend_variable_scope(value: str) -> ProjectConfigFrontendVariableScope:
+ if value in PROJECT_CONFIG_FRONTEND_VARIABLE_SCOPE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {PROJECT_CONFIG_FRONTEND_VARIABLE_SCOPE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/publish_sandbox_preset_request.py b/src/volcano_sdk/_generated/models/publish_sandbox_preset_request.py
new file mode 100644
index 00000000..f79e348b
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/publish_sandbox_preset_request.py
@@ -0,0 +1,100 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.publish_sandbox_preset_request_memory_mb import check_publish_sandbox_preset_request_memory_mb
+from ..models.publish_sandbox_preset_request_memory_mb import PublishSandboxPresetRequestMemoryMb
+from ..models.publish_sandbox_preset_request_preset import check_publish_sandbox_preset_request_preset
+from ..models.publish_sandbox_preset_request_preset import PublishSandboxPresetRequestPreset
+from typing import cast
+from uuid import UUID
+
+
+
+
+
+
+T = TypeVar("T", bound="PublishSandboxPresetRequest")
+
+
+
+@_attrs_define
+class PublishSandboxPresetRequest:
+ """
+ Attributes:
+ id (UUID):
+ preset (PublishSandboxPresetRequestPreset):
+ memory_mb (PublishSandboxPresetRequestMemoryMb):
+ deployment_id (UUID):
+ """
+
+ id: UUID
+ preset: PublishSandboxPresetRequestPreset
+ memory_mb: PublishSandboxPresetRequestMemoryMb
+ deployment_id: UUID
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ preset: str = self.preset
+
+ memory_mb: int = self.memory_mb
+
+ deployment_id = str(self.deployment_id)
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "id": id,
+ "preset": preset,
+ "memory_mb": memory_mb,
+ "deployment_id": deployment_id,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ preset = check_publish_sandbox_preset_request_preset(d.pop("preset"))
+
+
+
+
+ memory_mb = check_publish_sandbox_preset_request_memory_mb(d.pop("memory_mb"))
+
+
+
+
+ deployment_id = UUID(d.pop("deployment_id"))
+
+
+
+
+ publish_sandbox_preset_request = cls(
+ id=id,
+ preset=preset,
+ memory_mb=memory_mb,
+ deployment_id=deployment_id,
+ )
+
+ return publish_sandbox_preset_request
+
diff --git a/src/volcano_sdk/_generated/models/publish_sandbox_preset_request_memory_mb.py b/src/volcano_sdk/_generated/models/publish_sandbox_preset_request_memory_mb.py
new file mode 100644
index 00000000..89859165
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/publish_sandbox_preset_request_memory_mb.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+PublishSandboxPresetRequestMemoryMb = Literal[1024, 2048]
+
+PUBLISH_SANDBOX_PRESET_REQUEST_MEMORY_MB_VALUES: set[PublishSandboxPresetRequestMemoryMb] = { 1024, 2048, }
+
+def check_publish_sandbox_preset_request_memory_mb(value: int) -> PublishSandboxPresetRequestMemoryMb:
+ if value in PUBLISH_SANDBOX_PRESET_REQUEST_MEMORY_MB_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {PUBLISH_SANDBOX_PRESET_REQUEST_MEMORY_MB_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/publish_sandbox_preset_request_preset.py b/src/volcano_sdk/_generated/models/publish_sandbox_preset_request_preset.py
new file mode 100644
index 00000000..2011973c
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/publish_sandbox_preset_request_preset.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+PublishSandboxPresetRequestPreset = Literal['node22', 'python3.12']
+
+PUBLISH_SANDBOX_PRESET_REQUEST_PRESET_VALUES: set[PublishSandboxPresetRequestPreset] = { 'node22', 'python3.12', }
+
+def check_publish_sandbox_preset_request_preset(value: str) -> PublishSandboxPresetRequestPreset:
+ if value in PUBLISH_SANDBOX_PRESET_REQUEST_PRESET_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {PUBLISH_SANDBOX_PRESET_REQUEST_PRESET_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/replace_frontend_shared_variables_body.py b/src/volcano_sdk/_generated/models/replace_frontend_shared_variables_body.py
new file mode 100644
index 00000000..935f0234
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/replace_frontend_shared_variables_body.py
@@ -0,0 +1,88 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+from typing import cast
+
+
+
+
+
+
+T = TypeVar("T", bound="ReplaceFrontendSharedVariablesBody")
+
+
+
+@_attrs_define
+class ReplaceFrontendSharedVariablesBody:
+ """
+ Attributes:
+ frontend_shared_variables (list[str]):
+ expected_frontend_shared_variables (list[str] | Unset): When present, replace only if the current complete
+ frontend shared list matches this list.
+ expected_frontend_shared_variables_digest (str | Unset): SHA-256 of the sorted unique current shared names
+ joined by a newline. Use instead of expected_frontend_shared_variables for a compact conditional replacement.
+ """
+
+ frontend_shared_variables: list[str]
+ expected_frontend_shared_variables: list[str] | Unset = UNSET
+ expected_frontend_shared_variables_digest: str | Unset = UNSET
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ frontend_shared_variables = self.frontend_shared_variables
+
+
+
+ expected_frontend_shared_variables: list[str] | Unset = UNSET
+ if not isinstance(self.expected_frontend_shared_variables, Unset):
+ expected_frontend_shared_variables = self.expected_frontend_shared_variables
+
+
+
+ expected_frontend_shared_variables_digest = self.expected_frontend_shared_variables_digest
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "frontend_shared_variables": frontend_shared_variables,
+ })
+ if expected_frontend_shared_variables is not UNSET:
+ field_dict["expected_frontend_shared_variables"] = expected_frontend_shared_variables
+ if expected_frontend_shared_variables_digest is not UNSET:
+ field_dict["expected_frontend_shared_variables_digest"] = expected_frontend_shared_variables_digest
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ frontend_shared_variables = cast(list[str], d.pop("frontend_shared_variables"))
+
+
+ expected_frontend_shared_variables = cast(list[str], d.pop("expected_frontend_shared_variables", UNSET))
+
+
+ expected_frontend_shared_variables_digest = d.pop("expected_frontend_shared_variables_digest", UNSET)
+
+ replace_frontend_shared_variables_body = cls(
+ frontend_shared_variables=frontend_shared_variables,
+ expected_frontend_shared_variables=expected_frontend_shared_variables,
+ expected_frontend_shared_variables_digest=expected_frontend_shared_variables_digest,
+ )
+
+ return replace_frontend_shared_variables_body
+
diff --git a/src/volcano_sdk/_generated/models/resolve_function_response.py b/src/volcano_sdk/_generated/models/resolve_function_response.py
index d29a69ed..e8596032 100644
--- a/src/volcano_sdk/_generated/models/resolve_function_response.py
+++ b/src/volcano_sdk/_generated/models/resolve_function_response.py
@@ -8,6 +8,7 @@
from ..types import UNSET, Unset
+from ..types import UNSET, Unset
from uuid import UUID
@@ -26,11 +27,16 @@ class ResolveFunctionResponse:
name (str): DNS-safe function name
function_id (UUID): Canonical function ID used for invocation routing
cache_ttl_seconds (int): Suggested SDK cache TTL for this name-to-ID mapping
+ invoke_url (str | Unset): Canonical HTTPS endpoint for invoking this function. Use it as-is: it does not share a
+ domain with the API, so a host derived from the API URL will not reach the function. Omitted when the deployment
+ serves no public invocation domain, as in local development; invoke through POST /functions/{functionId}/invoke
+ instead.
"""
name: str
function_id: UUID
cache_ttl_seconds: int
+ invoke_url: str | Unset = UNSET
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
@@ -44,6 +50,8 @@ def to_dict(self) -> dict[str, Any]:
cache_ttl_seconds = self.cache_ttl_seconds
+ invoke_url = self.invoke_url
+
field_dict: dict[str, Any] = {}
field_dict.update(self.additional_properties)
@@ -52,6 +60,8 @@ def to_dict(self) -> dict[str, Any]:
"function_id": function_id,
"cache_ttl_seconds": cache_ttl_seconds,
})
+ if invoke_url is not UNSET:
+ field_dict["invoke_url"] = invoke_url
return field_dict
@@ -69,10 +79,13 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
cache_ttl_seconds = d.pop("cache_ttl_seconds")
+ invoke_url = d.pop("invoke_url", UNSET)
+
resolve_function_response = cls(
name=name,
function_id=function_id,
cache_ttl_seconds=cache_ttl_seconds,
+ invoke_url=invoke_url,
)
diff --git a/src/volcano_sdk/_generated/models/sandbox_access.py b/src/volcano_sdk/_generated/models/sandbox_access.py
new file mode 100644
index 00000000..f711cd44
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_access.py
@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxAccess")
+
+
+
+@_attrs_define
+class SandboxAccess:
+ """
+ Attributes:
+ url (str):
+ token (str):
+ expires_at (datetime.datetime):
+ """
+
+ url: str
+ token: str
+ expires_at: datetime.datetime
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ url = self.url
+
+ token = self.token
+
+ expires_at = self.expires_at.isoformat()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "url": url,
+ "token": token,
+ "expires_at": expires_at,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ url = d.pop("url")
+
+ token = d.pop("token")
+
+ expires_at = datetime.datetime.fromisoformat(d.pop("expires_at"))
+
+
+
+
+ sandbox_access = cls(
+ url=url,
+ token=token,
+ expires_at=expires_at,
+ )
+
+ return sandbox_access
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_access_request.py b/src/volcano_sdk/_generated/models/sandbox_access_request.py
new file mode 100644
index 00000000..74b8a6ba
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_access_request.py
@@ -0,0 +1,68 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxAccessRequest")
+
+
+
+@_attrs_define
+class SandboxAccessRequest:
+ """
+ Attributes:
+ port (int):
+ expires_in_seconds (int | Unset): Default: 300.
+ """
+
+ port: int
+ expires_in_seconds: int | Unset = 300
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ port = self.port
+
+ expires_in_seconds = self.expires_in_seconds
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "port": port,
+ })
+ if expires_in_seconds is not UNSET:
+ field_dict["expires_in_seconds"] = expires_in_seconds
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ port = d.pop("port")
+
+ expires_in_seconds = d.pop("expires_in_seconds", UNSET)
+
+ sandbox_access_request = cls(
+ port=port,
+ expires_in_seconds=expires_in_seconds,
+ )
+
+ return sandbox_access_request
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_capacity.py b/src/volcano_sdk/_generated/models/sandbox_capacity.py
new file mode 100644
index 00000000..edd6b91b
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_capacity.py
@@ -0,0 +1,66 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxCapacity")
+
+
+
+@_attrs_define
+class SandboxCapacity:
+ """
+ Attributes:
+ region (str):
+ allocated_memory_mb (int):
+ """
+
+ region: str
+ allocated_memory_mb: int
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ region = self.region
+
+ allocated_memory_mb = self.allocated_memory_mb
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "region": region,
+ "allocated_memory_mb": allocated_memory_mb,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ region = d.pop("region")
+
+ allocated_memory_mb = d.pop("allocated_memory_mb")
+
+ sandbox_capacity = cls(
+ region=region,
+ allocated_memory_mb=allocated_memory_mb,
+ )
+
+ return sandbox_capacity
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_capacity_list.py b/src/volcano_sdk/_generated/models/sandbox_capacity_list.py
new file mode 100644
index 00000000..e37a4650
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_capacity_list.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.sandbox_capacity import SandboxCapacity
+
+
+
+
+
+T = TypeVar("T", bound="SandboxCapacityList")
+
+
+
+@_attrs_define
+class SandboxCapacityList:
+ """
+ Attributes:
+ data (list[SandboxCapacity]):
+ """
+
+ data: list[SandboxCapacity]
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.sandbox_capacity import SandboxCapacity
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.sandbox_capacity import SandboxCapacity
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = SandboxCapacity.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ sandbox_capacity_list = cls(
+ data=data,
+ )
+
+ return sandbox_capacity_list
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_command_request.py b/src/volcano_sdk/_generated/models/sandbox_command_request.py
new file mode 100644
index 00000000..21054caa
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_command_request.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.sandbox_command_request_environment import SandboxCommandRequestEnvironment
+
+
+
+
+
+T = TypeVar("T", bound="SandboxCommandRequest")
+
+
+
+@_attrs_define
+class SandboxCommandRequest:
+ """
+ Attributes:
+ command (str):
+ timeout_seconds (int | Unset): Default: 60.
+ environment (SandboxCommandRequestEnvironment | Unset):
+ """
+
+ command: str
+ timeout_seconds: int | Unset = 60
+ environment: SandboxCommandRequestEnvironment | Unset = UNSET
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.sandbox_command_request_environment import SandboxCommandRequestEnvironment
+ command = self.command
+
+ timeout_seconds = self.timeout_seconds
+
+ environment: dict[str, Any] | Unset = UNSET
+ if not isinstance(self.environment, Unset):
+ environment = self.environment.to_dict()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "command": command,
+ })
+ if timeout_seconds is not UNSET:
+ field_dict["timeout_seconds"] = timeout_seconds
+ if environment is not UNSET:
+ field_dict["environment"] = environment
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.sandbox_command_request_environment import SandboxCommandRequestEnvironment
+ d = dict(src_dict)
+ command = d.pop("command")
+
+ timeout_seconds = d.pop("timeout_seconds", UNSET)
+
+ _environment = d.pop("environment", UNSET)
+ environment: SandboxCommandRequestEnvironment | Unset
+ if isinstance(_environment, Unset):
+ environment = UNSET
+ else:
+ environment = SandboxCommandRequestEnvironment.from_dict(_environment)
+
+
+
+
+ sandbox_command_request = cls(
+ command=command,
+ timeout_seconds=timeout_seconds,
+ environment=environment,
+ )
+
+ return sandbox_command_request
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_command_request_environment.py b/src/volcano_sdk/_generated/models/sandbox_command_request_environment.py
new file mode 100644
index 00000000..ec8d06c5
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_command_request_environment.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxCommandRequestEnvironment")
+
+
+
+@_attrs_define
+class SandboxCommandRequestEnvironment:
+ """
+ """
+
+ additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict)
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ sandbox_command_request_environment = cls(
+ )
+
+
+ sandbox_command_request_environment.additional_properties = d
+ return sandbox_command_request_environment
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> str:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: str) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/volcano_sdk/_generated/models/sandbox_command_result.py b/src/volcano_sdk/_generated/models/sandbox_command_result.py
new file mode 100644
index 00000000..6e0dad73
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_command_result.py
@@ -0,0 +1,98 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxCommandResult")
+
+
+
+@_attrs_define
+class SandboxCommandResult:
+ """
+ Attributes:
+ stdout (str):
+ stderr (str):
+ exit_code (int):
+ stdout_truncated (bool):
+ stderr_truncated (bool):
+ timed_out (bool):
+ """
+
+ stdout: str
+ stderr: str
+ exit_code: int
+ stdout_truncated: bool
+ stderr_truncated: bool
+ timed_out: bool
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ stdout = self.stdout
+
+ stderr = self.stderr
+
+ exit_code = self.exit_code
+
+ stdout_truncated = self.stdout_truncated
+
+ stderr_truncated = self.stderr_truncated
+
+ timed_out = self.timed_out
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "stdout": stdout,
+ "stderr": stderr,
+ "exit_code": exit_code,
+ "stdout_truncated": stdout_truncated,
+ "stderr_truncated": stderr_truncated,
+ "timed_out": timed_out,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ stdout = d.pop("stdout")
+
+ stderr = d.pop("stderr")
+
+ exit_code = d.pop("exit_code")
+
+ stdout_truncated = d.pop("stdout_truncated")
+
+ stderr_truncated = d.pop("stderr_truncated")
+
+ timed_out = d.pop("timed_out")
+
+ sandbox_command_result = cls(
+ stdout=stdout,
+ stderr=stderr,
+ exit_code=exit_code,
+ stdout_truncated=stdout_truncated,
+ stderr_truncated=stderr_truncated,
+ timed_out=timed_out,
+ )
+
+ return sandbox_command_result
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_deployment.py b/src/volcano_sdk/_generated/models/sandbox_deployment.py
new file mode 100644
index 00000000..bb499477
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_deployment.py
@@ -0,0 +1,94 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+from uuid import UUID
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxDeployment")
+
+
+
+@_attrs_define
+class SandboxDeployment:
+ """
+ Attributes:
+ id (UUID):
+ status (str):
+ created_at (datetime.datetime):
+ updated_at (datetime.datetime):
+ """
+
+ id: UUID
+ status: str
+ created_at: datetime.datetime
+ updated_at: datetime.datetime
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ status = self.status
+
+ created_at = self.created_at.isoformat()
+
+ updated_at = self.updated_at.isoformat()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "id": id,
+ "status": status,
+ "created_at": created_at,
+ "updated_at": updated_at,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ status = d.pop("status")
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+
+
+
+ updated_at = datetime.datetime.fromisoformat(d.pop("updated_at"))
+
+
+
+
+ sandbox_deployment = cls(
+ id=id,
+ status=status,
+ created_at=created_at,
+ updated_at=updated_at,
+ )
+
+ return sandbox_deployment
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_deployment_page.py b/src/volcano_sdk/_generated/models/sandbox_deployment_page.py
new file mode 100644
index 00000000..3fff6871
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_deployment_page.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.sandbox_deployment import SandboxDeployment
+ from ..models.sandbox_pagination import SandboxPagination
+
+
+
+
+
+T = TypeVar("T", bound="SandboxDeploymentPage")
+
+
+
+@_attrs_define
+class SandboxDeploymentPage:
+ """
+ Attributes:
+ data (list[SandboxDeployment]):
+ pagination (SandboxPagination):
+ """
+
+ data: list[SandboxDeployment]
+ pagination: SandboxPagination
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.sandbox_deployment import SandboxDeployment
+ from ..models.sandbox_pagination import SandboxPagination
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+ pagination = self.pagination.to_dict()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ "pagination": pagination,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.sandbox_deployment import SandboxDeployment
+ from ..models.sandbox_pagination import SandboxPagination
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = SandboxDeployment.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ pagination = SandboxPagination.from_dict(d.pop("pagination"))
+
+
+
+
+ sandbox_deployment_page = cls(
+ data=data,
+ pagination=pagination,
+ )
+
+ return sandbox_deployment_page
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_execution_result.py b/src/volcano_sdk/_generated/models/sandbox_execution_result.py
new file mode 100644
index 00000000..5991f241
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_execution_result.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from uuid import UUID
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxExecutionResult")
+
+
+
+@_attrs_define
+class SandboxExecutionResult:
+ """
+ Attributes:
+ stdout (str):
+ stderr (str):
+ exit_code (int):
+ stdout_truncated (bool):
+ stderr_truncated (bool):
+ timed_out (bool):
+ session_id (UUID):
+ region (str):
+ duration_ms (int):
+ """
+
+ stdout: str
+ stderr: str
+ exit_code: int
+ stdout_truncated: bool
+ stderr_truncated: bool
+ timed_out: bool
+ session_id: UUID
+ region: str
+ duration_ms: int
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ stdout = self.stdout
+
+ stderr = self.stderr
+
+ exit_code = self.exit_code
+
+ stdout_truncated = self.stdout_truncated
+
+ stderr_truncated = self.stderr_truncated
+
+ timed_out = self.timed_out
+
+ session_id = str(self.session_id)
+
+ region = self.region
+
+ duration_ms = self.duration_ms
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "stdout": stdout,
+ "stderr": stderr,
+ "exit_code": exit_code,
+ "stdout_truncated": stdout_truncated,
+ "stderr_truncated": stderr_truncated,
+ "timed_out": timed_out,
+ "session_id": session_id,
+ "region": region,
+ "duration_ms": duration_ms,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ stdout = d.pop("stdout")
+
+ stderr = d.pop("stderr")
+
+ exit_code = d.pop("exit_code")
+
+ stdout_truncated = d.pop("stdout_truncated")
+
+ stderr_truncated = d.pop("stderr_truncated")
+
+ timed_out = d.pop("timed_out")
+
+ session_id = UUID(d.pop("session_id"))
+
+
+
+
+ region = d.pop("region")
+
+ duration_ms = d.pop("duration_ms")
+
+ sandbox_execution_result = cls(
+ stdout=stdout,
+ stderr=stderr,
+ exit_code=exit_code,
+ stdout_truncated=stdout_truncated,
+ stderr_truncated=stderr_truncated,
+ timed_out=timed_out,
+ session_id=session_id,
+ region=region,
+ duration_ms=duration_ms,
+ )
+
+ return sandbox_execution_result
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_file_read_request.py b/src/volcano_sdk/_generated/models/sandbox_file_read_request.py
new file mode 100644
index 00000000..54a63ab6
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_file_read_request.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxFileReadRequest")
+
+
+
+@_attrs_define
+class SandboxFileReadRequest:
+ """
+ Attributes:
+ path (str):
+ """
+
+ path: str
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ path = self.path
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "path": path,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ path = d.pop("path")
+
+ sandbox_file_read_request = cls(
+ path=path,
+ )
+
+ return sandbox_file_read_request
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_file_result.py b/src/volcano_sdk/_generated/models/sandbox_file_result.py
new file mode 100644
index 00000000..898f929d
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_file_result.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxFileResult")
+
+
+
+@_attrs_define
+class SandboxFileResult:
+ """
+ Attributes:
+ data (str):
+ """
+
+ data: str
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ data = self.data
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ data = d.pop("data")
+
+ sandbox_file_result = cls(
+ data=data,
+ )
+
+ return sandbox_file_result
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_file_write_request.py b/src/volcano_sdk/_generated/models/sandbox_file_write_request.py
new file mode 100644
index 00000000..56e4e891
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_file_write_request.py
@@ -0,0 +1,66 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxFileWriteRequest")
+
+
+
+@_attrs_define
+class SandboxFileWriteRequest:
+ """
+ Attributes:
+ path (str):
+ data (str):
+ """
+
+ path: str
+ data: str
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ path = self.path
+
+ data = self.data
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "path": path,
+ "data": data,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ path = d.pop("path")
+
+ data = d.pop("data")
+
+ sandbox_file_write_request = cls(
+ path=path,
+ data=data,
+ )
+
+ return sandbox_file_write_request
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_pagination.py b/src/volcano_sdk/_generated/models/sandbox_pagination.py
new file mode 100644
index 00000000..79b2ec37
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_pagination.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxPagination")
+
+
+
+@_attrs_define
+class SandboxPagination:
+ """
+ Attributes:
+ limit (int):
+ has_more (bool):
+ next_cursor (str | Unset):
+ """
+
+ limit: int
+ has_more: bool
+ next_cursor: str | Unset = UNSET
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ limit = self.limit
+
+ has_more = self.has_more
+
+ next_cursor = self.next_cursor
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "limit": limit,
+ "has_more": has_more,
+ })
+ if next_cursor is not UNSET:
+ field_dict["next_cursor"] = next_cursor
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ limit = d.pop("limit")
+
+ has_more = d.pop("has_more")
+
+ next_cursor = d.pop("next_cursor", UNSET)
+
+ sandbox_pagination = cls(
+ limit=limit,
+ has_more=has_more,
+ next_cursor=next_cursor,
+ )
+
+ return sandbox_pagination
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_preset.py b/src/volcano_sdk/_generated/models/sandbox_preset.py
new file mode 100644
index 00000000..c6aa05da
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_preset.py
@@ -0,0 +1,99 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.sandbox_preset_memory_mb import check_sandbox_preset_memory_mb
+from ..models.sandbox_preset_memory_mb import SandboxPresetMemoryMb
+from typing import cast
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxPreset")
+
+
+
+@_attrs_define
+class SandboxPreset:
+ """
+ Attributes:
+ id (str):
+ runtime (str):
+ version (str):
+ memory_mb (SandboxPresetMemoryMb):
+ regions (list[str]):
+ """
+
+ id: str
+ runtime: str
+ version: str
+ memory_mb: SandboxPresetMemoryMb
+ regions: list[str]
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = self.id
+
+ runtime = self.runtime
+
+ version = self.version
+
+ memory_mb: int = self.memory_mb
+
+ regions = self.regions
+
+
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "id": id,
+ "runtime": runtime,
+ "version": version,
+ "memory_mb": memory_mb,
+ "regions": regions,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = d.pop("id")
+
+ runtime = d.pop("runtime")
+
+ version = d.pop("version")
+
+ memory_mb = check_sandbox_preset_memory_mb(d.pop("memory_mb"))
+
+
+
+
+ regions = cast(list[str], d.pop("regions"))
+
+
+ sandbox_preset = cls(
+ id=id,
+ runtime=runtime,
+ version=version,
+ memory_mb=memory_mb,
+ regions=regions,
+ )
+
+ return sandbox_preset
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_preset_list.py b/src/volcano_sdk/_generated/models/sandbox_preset_list.py
new file mode 100644
index 00000000..94b05b24
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_preset_list.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.sandbox_preset import SandboxPreset
+
+
+
+
+
+T = TypeVar("T", bound="SandboxPresetList")
+
+
+
+@_attrs_define
+class SandboxPresetList:
+ """
+ Attributes:
+ data (list[SandboxPreset]):
+ """
+
+ data: list[SandboxPreset]
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.sandbox_preset import SandboxPreset
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.sandbox_preset import SandboxPreset
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = SandboxPreset.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ sandbox_preset_list = cls(
+ data=data,
+ )
+
+ return sandbox_preset_list
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_preset_memory_mb.py b/src/volcano_sdk/_generated/models/sandbox_preset_memory_mb.py
new file mode 100644
index 00000000..e8d5db92
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_preset_memory_mb.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+SandboxPresetMemoryMb = Literal[1024, 2048]
+
+SANDBOX_PRESET_MEMORY_MB_VALUES: set[SandboxPresetMemoryMb] = { 1024, 2048, }
+
+def check_sandbox_preset_memory_mb(value: int) -> SandboxPresetMemoryMb:
+ if value in SANDBOX_PRESET_MEMORY_MB_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {SANDBOX_PRESET_MEMORY_MB_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/sandbox_session.py b/src/volcano_sdk/_generated/models/sandbox_session.py
new file mode 100644
index 00000000..3975df23
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_session.py
@@ -0,0 +1,170 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.sandbox_session_desired_state import check_sandbox_session_desired_state
+from ..models.sandbox_session_desired_state import SandboxSessionDesiredState
+from ..models.sandbox_session_state import check_sandbox_session_state
+from ..models.sandbox_session_state import SandboxSessionState
+from ..types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxSession")
+
+
+
+@_attrs_define
+class SandboxSession:
+ """
+ Attributes:
+ id (UUID):
+ project_id (UUID):
+ sandbox_id (UUID):
+ state (SandboxSessionState):
+ desired_state (SandboxSessionDesiredState):
+ region (str):
+ memory_mb (int):
+ created_at (datetime.datetime):
+ expires_at (datetime.datetime):
+ started_at (datetime.datetime | Unset):
+ """
+
+ id: UUID
+ project_id: UUID
+ sandbox_id: UUID
+ state: SandboxSessionState
+ desired_state: SandboxSessionDesiredState
+ region: str
+ memory_mb: int
+ created_at: datetime.datetime
+ expires_at: datetime.datetime
+ started_at: datetime.datetime | Unset = UNSET
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ project_id = str(self.project_id)
+
+ sandbox_id = str(self.sandbox_id)
+
+ state: str = self.state
+
+ desired_state: str = self.desired_state
+
+ region = self.region
+
+ memory_mb = self.memory_mb
+
+ created_at = self.created_at.isoformat()
+
+ expires_at = self.expires_at.isoformat()
+
+ started_at: str | Unset = UNSET
+ if not isinstance(self.started_at, Unset):
+ started_at = self.started_at.isoformat()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "id": id,
+ "project_id": project_id,
+ "sandbox_id": sandbox_id,
+ "state": state,
+ "desired_state": desired_state,
+ "region": region,
+ "memory_mb": memory_mb,
+ "created_at": created_at,
+ "expires_at": expires_at,
+ })
+ if started_at is not UNSET:
+ field_dict["started_at"] = started_at
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ project_id = UUID(d.pop("project_id"))
+
+
+
+
+ sandbox_id = UUID(d.pop("sandbox_id"))
+
+
+
+
+ state = check_sandbox_session_state(d.pop("state"))
+
+
+
+
+ desired_state = check_sandbox_session_desired_state(d.pop("desired_state"))
+
+
+
+
+ region = d.pop("region")
+
+ memory_mb = d.pop("memory_mb")
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+
+
+
+ expires_at = datetime.datetime.fromisoformat(d.pop("expires_at"))
+
+
+
+
+ _started_at = d.pop("started_at", UNSET)
+ started_at: datetime.datetime | Unset
+ if isinstance(_started_at, Unset):
+ started_at = UNSET
+ else:
+ started_at = datetime.datetime.fromisoformat(_started_at)
+
+
+
+
+ sandbox_session = cls(
+ id=id,
+ project_id=project_id,
+ sandbox_id=sandbox_id,
+ state=state,
+ desired_state=desired_state,
+ region=region,
+ memory_mb=memory_mb,
+ created_at=created_at,
+ expires_at=expires_at,
+ started_at=started_at,
+ )
+
+ return sandbox_session
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_session_desired_state.py b/src/volcano_sdk/_generated/models/sandbox_session_desired_state.py
new file mode 100644
index 00000000..5030758f
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_session_desired_state.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+SandboxSessionDesiredState = Literal['running', 'suspended', 'terminated']
+
+SANDBOX_SESSION_DESIRED_STATE_VALUES: set[SandboxSessionDesiredState] = { 'running', 'suspended', 'terminated', }
+
+def check_sandbox_session_desired_state(value: str) -> SandboxSessionDesiredState:
+ if value in SANDBOX_SESSION_DESIRED_STATE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {SANDBOX_SESSION_DESIRED_STATE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/sandbox_session_page.py b/src/volcano_sdk/_generated/models/sandbox_session_page.py
new file mode 100644
index 00000000..b1a88b37
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_session_page.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.sandbox_pagination import SandboxPagination
+ from ..models.sandbox_session import SandboxSession
+
+
+
+
+
+T = TypeVar("T", bound="SandboxSessionPage")
+
+
+
+@_attrs_define
+class SandboxSessionPage:
+ """
+ Attributes:
+ data (list[SandboxSession]):
+ pagination (SandboxPagination):
+ """
+
+ data: list[SandboxSession]
+ pagination: SandboxPagination
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.sandbox_pagination import SandboxPagination
+ from ..models.sandbox_session import SandboxSession
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+ pagination = self.pagination.to_dict()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ "pagination": pagination,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.sandbox_pagination import SandboxPagination
+ from ..models.sandbox_session import SandboxSession
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = SandboxSession.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ pagination = SandboxPagination.from_dict(d.pop("pagination"))
+
+
+
+
+ sandbox_session_page = cls(
+ data=data,
+ pagination=pagination,
+ )
+
+ return sandbox_session_page
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_session_state.py b/src/volcano_sdk/_generated/models/sandbox_session_state.py
new file mode 100644
index 00000000..ea68077a
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_session_state.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+SandboxSessionState = Literal['resuming', 'running', 'starting', 'suspended', 'suspending', 'terminated', 'terminating', 'unknown']
+
+SANDBOX_SESSION_STATE_VALUES: set[SandboxSessionState] = { 'resuming', 'running', 'starting', 'suspended', 'suspending', 'terminated', 'terminating', 'unknown', }
+
+def check_sandbox_session_state(value: str) -> SandboxSessionState:
+ if value in SANDBOX_SESSION_STATE_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {SANDBOX_SESSION_STATE_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/sandbox_subject_grant_request.py b/src/volcano_sdk/_generated/models/sandbox_subject_grant_request.py
new file mode 100644
index 00000000..13eb3896
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_subject_grant_request.py
@@ -0,0 +1,63 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxSubjectGrantRequest")
+
+
+
+@_attrs_define
+class SandboxSubjectGrantRequest:
+ """
+ Attributes:
+ expires_at (datetime.datetime):
+ """
+
+ expires_at: datetime.datetime
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ expires_at = self.expires_at.isoformat()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "expires_at": expires_at,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ expires_at = datetime.datetime.fromisoformat(d.pop("expires_at"))
+
+
+
+
+ sandbox_subject_grant_request = cls(
+ expires_at=expires_at,
+ )
+
+ return sandbox_subject_grant_request
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_template.py b/src/volcano_sdk/_generated/models/sandbox_template.py
new file mode 100644
index 00000000..e4b722b4
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_template.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from ..models.sandbox_template_status import check_sandbox_template_status
+from ..models.sandbox_template_status import SandboxTemplateStatus
+from ..types import UNSET, Unset
+from typing import cast
+from uuid import UUID
+import datetime
+
+
+
+
+
+
+T = TypeVar("T", bound="SandboxTemplate")
+
+
+
+@_attrs_define
+class SandboxTemplate:
+ """
+ Attributes:
+ id (UUID):
+ project_id (UUID):
+ name (str):
+ status (SandboxTemplateStatus):
+ created_at (datetime.datetime):
+ preset (str | Unset):
+ memory_mb (int | Unset):
+ """
+
+ id: UUID
+ project_id: UUID
+ name: str
+ status: SandboxTemplateStatus
+ created_at: datetime.datetime
+ preset: str | Unset = UNSET
+ memory_mb: int | Unset = UNSET
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ id = str(self.id)
+
+ project_id = str(self.project_id)
+
+ name = self.name
+
+ status: str = self.status
+
+ created_at = self.created_at.isoformat()
+
+ preset = self.preset
+
+ memory_mb = self.memory_mb
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "id": id,
+ "project_id": project_id,
+ "name": name,
+ "status": status,
+ "created_at": created_at,
+ })
+ if preset is not UNSET:
+ field_dict["preset"] = preset
+ if memory_mb is not UNSET:
+ field_dict["memory_mb"] = memory_mb
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ id = UUID(d.pop("id"))
+
+
+
+
+ project_id = UUID(d.pop("project_id"))
+
+
+
+
+ name = d.pop("name")
+
+ status = check_sandbox_template_status(d.pop("status"))
+
+
+
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+
+
+
+ preset = d.pop("preset", UNSET)
+
+ memory_mb = d.pop("memory_mb", UNSET)
+
+ sandbox_template = cls(
+ id=id,
+ project_id=project_id,
+ name=name,
+ status=status,
+ created_at=created_at,
+ preset=preset,
+ memory_mb=memory_mb,
+ )
+
+ return sandbox_template
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_template_page.py b/src/volcano_sdk/_generated/models/sandbox_template_page.py
new file mode 100644
index 00000000..5b8ecda6
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_template_page.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.sandbox_pagination import SandboxPagination
+ from ..models.sandbox_template import SandboxTemplate
+
+
+
+
+
+T = TypeVar("T", bound="SandboxTemplatePage")
+
+
+
+@_attrs_define
+class SandboxTemplatePage:
+ """
+ Attributes:
+ data (list[SandboxTemplate]):
+ pagination (SandboxPagination):
+ """
+
+ data: list[SandboxTemplate]
+ pagination: SandboxPagination
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.sandbox_pagination import SandboxPagination
+ from ..models.sandbox_template import SandboxTemplate
+ data = []
+ for data_item_data in self.data:
+ data_item = data_item_data.to_dict()
+ data.append(data_item)
+
+
+
+ pagination = self.pagination.to_dict()
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "data": data,
+ "pagination": pagination,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.sandbox_pagination import SandboxPagination
+ from ..models.sandbox_template import SandboxTemplate
+ d = dict(src_dict)
+ data = []
+ _data = d.pop("data")
+ for data_item_data in (_data):
+ data_item = SandboxTemplate.from_dict(data_item_data)
+
+
+
+ data.append(data_item)
+
+
+ pagination = SandboxPagination.from_dict(d.pop("pagination"))
+
+
+
+
+ sandbox_template_page = cls(
+ data=data,
+ pagination=pagination,
+ )
+
+ return sandbox_template_page
+
diff --git a/src/volcano_sdk/_generated/models/sandbox_template_status.py b/src/volcano_sdk/_generated/models/sandbox_template_status.py
new file mode 100644
index 00000000..4bdafa9a
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/sandbox_template_status.py
@@ -0,0 +1,10 @@
+from typing import Literal
+
+SandboxTemplateStatus = Literal['deleting', 'ready', 'unavailable']
+
+SANDBOX_TEMPLATE_STATUS_VALUES: set[SandboxTemplateStatus] = { 'deleting', 'ready', 'unavailable', }
+
+def check_sandbox_template_status(value: str) -> SandboxTemplateStatus:
+ if value in SANDBOX_TEMPLATE_STATUS_VALUES:
+ return value
+ raise TypeError(f"Unexpected value {value!r}. Expected one of {SANDBOX_TEMPLATE_STATUS_VALUES!r}")
diff --git a/src/volcano_sdk/_generated/models/update_sandbox_template_request.py b/src/volcano_sdk/_generated/models/update_sandbox_template_request.py
new file mode 100644
index 00000000..2d2c3d15
--- /dev/null
+++ b/src/volcano_sdk/_generated/models/update_sandbox_template_request.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+from ..types import UNSET, Unset
+
+
+
+
+
+
+
+T = TypeVar("T", bound="UpdateSandboxTemplateRequest")
+
+
+
+@_attrs_define
+class UpdateSandboxTemplateRequest:
+ """
+ Attributes:
+ name (str):
+ """
+
+ name: str
+
+
+
+
+
+ def to_dict(self) -> dict[str, Any]:
+ name = self.name
+
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update({
+ "name": name,
+ })
+
+ return field_dict
+
+
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ name = d.pop("name")
+
+ update_sandbox_template_request = cls(
+ name=name,
+ )
+
+ return update_sandbox_template_request
+
diff --git a/src/volcano_sdk/_generated/models/variable.py b/src/volcano_sdk/_generated/models/variable.py
index 81d29cc9..bc164bb9 100644
--- a/src/volcano_sdk/_generated/models/variable.py
+++ b/src/volcano_sdk/_generated/models/variable.py
@@ -39,6 +39,7 @@ class Variable:
shared (bool | Unset): Include this name in the project's shared function variables. Omission preserves existing
membership; new variables default to true for legacy clients. Send false explicitly to create a non-shared
variable.
+ frontend_shared (bool | Unset): Whether this name is in the project's shared frontend-variable list.
status (VariableStatus | Unset): Latest project variable propagation status, when a sync has run.
current_sync_id (UUID | Unset): Identifier of the latest variable propagation sync.
provisioning_started_at (datetime.datetime | Unset): Timestamp when the current variable propagation phase
@@ -54,6 +55,7 @@ class Variable:
created_at: datetime.datetime
updated_at: datetime.datetime
shared: bool | Unset = UNSET
+ frontend_shared: bool | Unset = UNSET
status: VariableStatus | Unset = UNSET
current_sync_id: UUID | Unset = UNSET
provisioning_started_at: datetime.datetime | Unset = UNSET
@@ -79,6 +81,8 @@ def to_dict(self) -> dict[str, Any]:
shared = self.shared
+ frontend_shared = self.frontend_shared
+
status: str | Unset = UNSET
if not isinstance(self.status, Unset):
status = self.status
@@ -110,6 +114,8 @@ def to_dict(self) -> dict[str, Any]:
})
if shared is not UNSET:
field_dict["shared"] = shared
+ if frontend_shared is not UNSET:
+ field_dict["frontend_shared"] = frontend_shared
if status is not UNSET:
field_dict["status"] = status
if current_sync_id is not UNSET:
@@ -152,6 +158,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
shared = d.pop("shared", UNSET)
+ frontend_shared = d.pop("frontend_shared", UNSET)
+
_status = d.pop("status", UNSET)
status: VariableStatus | Unset
if isinstance(_status, Unset):
@@ -200,6 +208,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
created_at=created_at,
updated_at=updated_at,
shared=shared,
+ frontend_shared=frontend_shared,
status=status,
current_sync_id=current_sync_id,
provisioning_started_at=provisioning_started_at,
diff --git a/src/volcano_sdk/_sandbox.py b/src/volcano_sdk/_sandbox.py
new file mode 100644
index 00000000..a43c5132
--- /dev/null
+++ b/src/volcano_sdk/_sandbox.py
@@ -0,0 +1,224 @@
+"""Internal Sandbox validation and transport boundary."""
+
+from __future__ import annotations
+
+from collections.abc import Callable, Mapping
+from typing import TYPE_CHECKING, Protocol, cast, runtime_checkable
+from uuid import UUID, uuid4
+
+from ._transport import TransportResponse, response_payload
+from .errors import ValidationError
+
+if TYPE_CHECKING:
+ from ._transport_sandbox import SandboxRequest
+ from .models import JSONValue
+from .sandbox_models import SandboxCommandResult
+
+_STATES = frozenset(
+ {
+ "starting",
+ "running",
+ "suspending",
+ "suspended",
+ "resuming",
+ "terminating",
+ "terminated",
+ "unknown",
+ }
+)
+
+
+def identifier(value: str) -> str:
+ """Normalize a UUID before any request is sent.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ ValidationError: If the supplied value violates the Sandbox contract.
+
+ """
+ try:
+ return str(UUID(value))
+ except (ValueError, AttributeError, TypeError) as error:
+ message = "Sandbox resource and request IDs must be UUIDs"
+ raise ValidationError(message) from error
+
+
+def record(value: object) -> Mapping[str, object]:
+ """Validate a response object.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ TypeError: If the supplied value violates the Sandbox contract.
+
+ """
+ if not isinstance(value, Mapping):
+ message = "Invalid Sandbox response"
+ raise TypeError(message)
+ return cast("Mapping[str, object]", value)
+
+
+def text(value: object) -> str:
+ """Validate a response text field.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ TypeError: If the supplied value violates the Sandbox contract.
+
+ """
+ if not isinstance(value, str):
+ message = "Invalid Sandbox text field"
+ raise TypeError(message)
+ return value
+
+
+def integer(value: object) -> int:
+ """Validate a response integer without accepting booleans.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ TypeError: If the supplied value violates the Sandbox contract.
+
+ """
+ if type(value) is not int:
+ message = "Invalid Sandbox integer field"
+ raise TypeError(message)
+ return value
+
+
+def flag(value: object) -> bool:
+ """Validate a response boolean.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ TypeError: If the supplied value violates the Sandbox contract.
+
+ """
+ if not isinstance(value, bool):
+ message = "Invalid Sandbox flag"
+ raise TypeError(message)
+ return value
+
+
+def state(value: object) -> str:
+ """Validate a lifecycle state.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ TypeError: If the supplied value violates the Sandbox contract.
+
+ """
+ result = text(value)
+ if result not in _STATES:
+ message = "Invalid Sandbox state"
+ raise TypeError(message)
+ return result
+
+
+def command_result(value: object) -> SandboxCommandResult:
+ """Decode command output without interpreting the exit code as an API error.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ data = record(value)
+ return SandboxCommandResult(
+ text(data.get("stdout")),
+ text(data.get("stderr")),
+ integer(data.get("exit_code")),
+ flag(data.get("timed_out")),
+ flag(data.get("stdout_truncated")),
+ flag(data.get("stderr_truncated")),
+ )
+
+
+def selector(options: Mapping[str, object]) -> dict[str, JSONValue]:
+ """Preserve omitted memory so named Sandboxes retain their configuration.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ ValidationError: If the supplied value violates the Sandbox contract.
+
+ """
+ if (options.get("preset") is None) == (options.get("sandbox_id") is None):
+ message = "Choose exactly one preset or sandbox_id"
+ raise ValidationError(message)
+ data: dict[str, JSONValue] = {"region": text(options.get("region"))}
+ for key in ("preset", "sandbox_id", "memory_mb"):
+ if key in options:
+ value = options[key]
+ data[key] = integer(value) if key == "memory_mb" else text(value)
+ if "sandbox_id" in data:
+ data["sandbox_id"] = identifier(text(data["sandbox_id"]))
+ return data
+
+
+def command_body(command: str, options: Mapping[str, object]) -> dict[str, JSONValue]:
+ """Copy command options for a single dispatch.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ body: dict[str, JSONValue] = {"command": command}
+ if "timeout_seconds" in options:
+ body["timeout_seconds"] = integer(options["timeout_seconds"])
+ if "environment" in options:
+ body["environment"] = {
+ key: text(value) for key, value in record(options["environment"]).items()
+ }
+ return body
+
+
+@runtime_checkable
+class SandboxTransport(Protocol):
+ """Internal transport implemented using generated operations."""
+
+ def sandbox_request(
+ self, *, authorization: str, request: SandboxRequest
+ ) -> TransportResponse:
+ """Dispatch once without replaying uncertain side effects."""
+ ...
+
+
+class SandboxRequests:
+ """Bind current credentials without falling back to an anonymous key."""
+
+ def __init__(self, dispatch: Callable[[SandboxRequest], TransportResponse]) -> None:
+ """Bind credential-aware dispatch."""
+ self.dispatch: Callable[[SandboxRequest], TransportResponse] = dispatch
+
+ def send(self, request: SandboxRequest, status: int = 200) -> object:
+ """Dispatch through normal typed error handling.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ response = self.dispatch(request)
+ return response_payload(response, status)
+
+
+def request_id(options: Mapping[str, object]) -> str:
+ """Keep explicit retry identities stable; allocate once otherwise.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ value = options.get("request_id")
+ return identifier(text(value)) if value is not None else str(uuid4())
diff --git a/src/volcano_sdk/_tests/test_sandboxes.py b/src/volcano_sdk/_tests/test_sandboxes.py
new file mode 100644
index 00000000..7005c7f6
--- /dev/null
+++ b/src/volcano_sdk/_tests/test_sandboxes.py
@@ -0,0 +1,563 @@
+from __future__ import annotations
+
+import base64
+import binascii
+import json
+from collections import deque
+from typing import TYPE_CHECKING
+from uuid import UUID, uuid4
+
+import httpx
+import pytest
+from typing_extensions import override
+
+from volcano_sdk import (
+ AuthenticationError,
+ ConflictError,
+ RateLimitedError,
+ SandboxCommandResult,
+ Sandboxes,
+ SandboxSession,
+ Session,
+ SessionChangedError,
+ TransportError,
+ ValidationError,
+ VolcanoClient,
+)
+from volcano_sdk._transport import GeneratedTransport
+
+from .session_fixtures import access_token
+from .transport_fixtures import RejectingTransport
+
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from volcano_sdk._session_operations import SessionOperations
+ from volcano_sdk.sandbox_models import SandboxExecOptions
+
+PROJECT = "00000000-0000-4000-8000-000000000001"
+SESSION = "00000000-0000-4000-8000-000000000002"
+SUBJECT = "00000000-0000-4000-8000-000000000003"
+KEY = "00000000-0000-4000-8000-000000000004"
+
+
+def session_body(state: str = "running") -> dict[str, object]:
+ return {
+ "id": SESSION,
+ "project_id": PROJECT,
+ "region": "aws-us-east-1",
+ "state": state,
+ "expires_at": "2026-09-25T00:00:00Z",
+ }
+
+
+def command_body() -> dict[str, object]:
+ return {
+ "stdout": "hello",
+ "stderr": "err",
+ "exit_code": 7,
+ "timed_out": False,
+ "stdout_truncated": False,
+ "stderr_truncated": True,
+ }
+
+
+class SandboxHTTP:
+ def __init__(self) -> None:
+ self.requests: list[httpx.Request] = []
+ self.responses: deque[httpx.Response] = deque()
+ self.failure: Exception | None = None
+ self.client: VolcanoClient = VolcanoClient(
+ anon_key="anon",
+ service_key="service",
+ _transport=GeneratedTransport(
+ api_url="https://sandbox.test",
+ httpx_transport=httpx.MockTransport(self.handle),
+ ),
+ )
+
+ def handle(self, request: httpx.Request) -> httpx.Response:
+ self.requests.append(request)
+ if self.failure is not None:
+ raise self.failure
+ return self.responses.popleft()
+
+ def reply(
+ self,
+ payload: object = None,
+ status: int = 200,
+ headers: Mapping[str, str] | None = None,
+ ) -> None:
+ self.responses.append(httpx.Response(status, json=payload, headers=headers))
+
+
+def test_sandbox_selectors_and_mutation_identity() -> None:
+ server = SandboxHTTP()
+ assert isinstance(server.client.sandboxes, Sandboxes)
+ for _ in range(2):
+ server.reply(session_body(), 201)
+ session = server.client.sandboxes.create(
+ PROJECT,
+ region="aws-us-east-1",
+ sandbox_id=SUBJECT,
+ request_id=KEY,
+ max_duration_seconds=300,
+ idle_timeout_seconds=30,
+ )
+ assert session.id == SESSION
+ first, second = server.requests
+ assert first.headers["Idempotency-Key"] == second.headers["Idempotency-Key"] == KEY
+ assert first.headers["Authorization"] == "Bearer service"
+ assert first.url.path == f"/projects/{PROJECT}/sandbox-sessions"
+ assert json.loads(first.content) == {
+ "sandbox_id": SUBJECT,
+ "region": "aws-us-east-1",
+ "max_duration_seconds": 300,
+ "idle_timeout_seconds": 30,
+ }
+ assert first.extensions["timeout"]["read"] >= 180
+
+
+def test_sandbox_one_shot_and_session_execution() -> None:
+ server = SandboxHTTP()
+ server.reply(
+ command_body()
+ | {"session_id": SESSION, "region": "aws-us-east-1", "duration_ms": 42}
+ )
+ result = server.client.sandboxes.exec(
+ PROJECT,
+ "exit 7",
+ preset="python3.12",
+ region="aws-us-east-1",
+ memory_mb=2048,
+ environment={"X": "value"},
+ timeout_seconds=30,
+ )
+ assert result.exit_code == 7
+ assert result.stderr_truncated
+ assert result.session_id == SESSION
+ assert result.duration_ms == 42
+ assert (
+ str(UUID(server.requests[0].headers["Idempotency-Key"]))
+ == server.requests[0].headers["Idempotency-Key"]
+ )
+ assert json.loads(server.requests[0].content) == {
+ "region": "aws-us-east-1",
+ "preset": "python3.12",
+ "command": "exit 7",
+ "memory_mb": 2048,
+ "environment": {"X": "value"},
+ "timeout_seconds": 30,
+ }
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ server.reply(command_body())
+ command = session.exec("exit 7", request_id=KEY, timeout_seconds=3600)
+ assert command == SandboxCommandResult(
+ "hello",
+ "err",
+ 7,
+ timed_out=False,
+ stdout_truncated=False,
+ stderr_truncated=True,
+ )
+ assert server.requests[-1].extensions["timeout"]["read"] == 3720
+ assert server.requests[-1].headers["Idempotency-Key"] == KEY
+
+
+def test_sandbox_lifecycle_files_access_and_grants() -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ for operation, state in [
+ (session.suspend, "suspending"),
+ (session.resume, "resuming"),
+ (session.terminate, "terminating"),
+ ]:
+ server.reply(session_body(state), 202)
+ assert operation() is session
+ assert session.state == state
+ server.reply(session_body("running"))
+ assert session.refresh().state == "running"
+ data = bytes(range(256))
+ server.reply(status=204)
+ session.files.write("/workspace/data", data)
+ assert (
+ json.loads(server.requests[-1].content)["data"]
+ == base64.b64encode(data).decode()
+ )
+ server.reply({"data": base64.b64encode(data).decode()})
+ assert session.files.read("/workspace/data") == data
+ server.reply(
+ {
+ "url": "https://sandbox.test/access",
+ "token": "secret",
+ "expires_at": "tomorrow",
+ }
+ )
+ access = session.access(8080)
+ assert access.token == "secret"
+ assert "secret" not in repr(access)
+ server.reply(status=204)
+ server.client.sandboxes.grant(SESSION, SUBJECT, "2026-09-25T00:00:00Z")
+ assert server.requests[-1].method == "PUT"
+ assert server.requests[-1].url.path.endswith(f"/grants/{SUBJECT}")
+ server.reply(status=204)
+ server.client.sandboxes.revoke(SESSION, SUBJECT)
+ assert server.requests[-1].method == "DELETE"
+
+
+def test_sandbox_credentials_and_typed_failures() -> None:
+ server = SandboxHTTP()
+ _ = server.client.auth.set_session(Session("user-access", "refresh", "user"))
+ server.reply(session_body())
+ _ = server.client.sandboxes.get(SESSION)
+ assert server.requests[-1].headers["Authorization"] == "Bearer user-access"
+ for status, error in [(409, ConflictError), (429, RateLimitedError)]:
+ server.reply(
+ {"error": "denied", "code": "sandbox_denied"}, status, {"Retry-After": "7"}
+ )
+ with pytest.raises(error) as caught:
+ _ = server.client.sandboxes.get(SESSION)
+ assert caught.value.code == "sandbox_denied"
+ assert caught.value.status == status
+ assert caught.value.retry_after == (7 if status == 429 else None)
+ server.failure = httpx.ReadTimeout("lost response")
+ before = len(server.requests)
+ with pytest.raises(TransportError):
+ _ = server.client.sandboxes.exec(
+ PROJECT, "run", region="aws-us-east-1", preset="python3.12", request_id=KEY
+ )
+ assert len(server.requests) == before + 1
+ client = VolcanoClient(anon_key="anon")
+ with pytest.raises(AuthenticationError) as missing:
+ _ = client.sandboxes.get(SESSION)
+ assert missing.value.status == 401
+ assert str(missing.value) == "No service key configured"
+
+
+def test_sandbox_rejects_invalid_local_inputs() -> None:
+ server = SandboxHTTP()
+ options_list: list[SandboxExecOptions] = [
+ {"region": "aws-us-east-1"},
+ {"region": "aws-us-east-1", "preset": "python3.12", "sandbox_id": SUBJECT},
+ {"region": "aws-us-east-1", "sandbox_id": "bad"},
+ {"region": "aws-us-east-1", "preset": "python3.12", "request_id": "bad"},
+ ]
+ for options in options_list:
+ with pytest.raises(ValidationError):
+ _ = server.client.sandboxes.exec(PROJECT, "run", **options)
+ with pytest.raises(ValidationError):
+ _ = server.client.sandboxes.get("../escape")
+ assert not server.requests
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ with pytest.raises(ValidationError, match=r"^Sandbox files are limited to 8 MiB$"):
+ session.files.write("/workspace/large", bytes(8 * 1024 * 1024 + 1))
+
+
+def test_sandbox_context_requests_cleanup_and_checks_identity() -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ server.reply(session_body("terminating"), 202)
+ with pytest.raises(ValueError, match="body failed"):
+ fail_inside_session(session)
+ server.reply(session_body("terminated"))
+ _ = session.refresh()
+ with session:
+ pass
+ count = len(server.requests)
+ server.reply(session_body() | {"id": str(uuid4())})
+ with pytest.raises(TypeError, match=r"^Sandbox session identity changed$"):
+ _ = session.refresh()
+ assert len(server.requests) == count + 1
+ assert session.state == "terminated"
+
+
+def test_sandbox_catalog() -> None:
+ server = SandboxHTTP()
+ server.reply(
+ {
+ "data": [
+ {"id": "python3.12", "memory_mb": 2048, "regions": ["aws-us-east-1"]}
+ ]
+ }
+ )
+ (preset,) = server.client.sandboxes.presets()
+ assert (preset.id, preset.memory_mb, preset.regions) == (
+ "python3.12",
+ 2048,
+ ("aws-us-east-1",),
+ )
+
+
+def fail_inside_session(session: SandboxSession) -> None:
+ with session as owned:
+ assert owned is session
+ message = "body failed"
+ raise ValueError(message)
+
+
+@pytest.mark.parametrize(
+ "payload",
+ [
+ None,
+ [],
+ {},
+ {"data": {}},
+ {"data": [{"id": "python3.12", "memory_mb": 2048, "regions": "bad"}]},
+ ],
+)
+def test_sandbox_rejects_invalid_catalog(payload: object) -> None:
+ server = SandboxHTTP()
+ server.reply(payload)
+ with pytest.raises(TypeError):
+ _ = server.client.sandboxes.presets()
+
+
+@pytest.mark.parametrize(
+ ("field", "value"), [("stdout", 1), ("exit_code", True), ("timed_out", "false")]
+)
+def test_sandbox_rejects_invalid_command_response(field: str, value: object) -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ server.reply(command_body() | {field: value})
+ with pytest.raises(TypeError):
+ _ = session.exec("run")
+
+
+@pytest.mark.parametrize(
+ ("field", "value"), [("state", "invalid"), ("expires_at", None)]
+)
+def test_sandbox_refresh_preserves_state_on_invalid_response(
+ field: str, value: object
+) -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ server.reply(session_body("terminated") | {field: value})
+ with pytest.raises(TypeError):
+ _ = session.refresh()
+ assert session.state == "running"
+
+
+def test_sandbox_create_preserves_server_lifecycle_defaults() -> None:
+ server = SandboxHTTP()
+ server.reply(session_body(), 201)
+ _ = server.client.sandboxes.create(
+ PROJECT, region="aws-us-east-1", preset="python3.12"
+ )
+ assert json.loads(server.requests[-1].content) == {
+ "region": "aws-us-east-1",
+ "preset": "python3.12",
+ }
+
+
+@pytest.mark.parametrize(
+ ("payload", "message"),
+ [
+ (None, "Invalid Sandbox response"),
+ ({}, "Invalid Sandbox preset catalog"),
+ ({"data": [{"regions": None}]}, "Invalid Sandbox regions"),
+ ],
+)
+def test_sandbox_catalog_reports_invalid_wire_shape(
+ payload: object, message: str
+) -> None:
+ server = SandboxHTTP()
+ server.reply(payload)
+ with pytest.raises(TypeError) as caught:
+ _ = server.client.sandboxes.presets()
+ assert str(caught.value) == message
+
+
+@pytest.mark.parametrize(
+ ("field", "value", "message"),
+ [
+ ("stdout", 1, "Invalid Sandbox text field"),
+ ("exit_code", True, "Invalid Sandbox integer field"),
+ ("timed_out", "false", "Invalid Sandbox flag"),
+ ],
+)
+def test_sandbox_command_reports_invalid_wire_field(
+ field: str, value: object, message: str
+) -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ server.reply(command_body() | {field: value})
+ with pytest.raises(TypeError) as caught:
+ _ = session.exec("run")
+ assert str(caught.value) == message
+ assert server.requests[-1].extensions["timeout"]["read"] == 180
+
+
+def test_sandbox_invalid_identity_and_state_are_actionable() -> None:
+ server = SandboxHTTP()
+ with pytest.raises(
+ ValidationError, match=r"^Sandbox resource and request IDs must be UUIDs$"
+ ):
+ _ = server.client.sandboxes.get("bad")
+ server.reply(session_body("invalid"))
+ with pytest.raises(TypeError, match=r"^Invalid Sandbox state$"):
+ _ = server.client.sandboxes.get(SESSION)
+ with pytest.raises(
+ ValidationError, match=r"^Choose exactly one preset or sandbox_id$"
+ ):
+ _ = server.client.sandboxes.create(PROJECT, region="aws-us-east-1")
+
+
+def test_sandbox_file_boundary_and_invalid_encoding() -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ session = server.client.sandboxes.get(SESSION)
+ data = bytes(8 * 1024 * 1024)
+ server.reply(status=204)
+ session.files.write("/workspace/boundary", data)
+ assert json.loads(server.requests[-1].content) == {
+ "path": "/workspace/boundary",
+ "data": base64.b64encode(data).decode(),
+ }
+ server.reply({"data": "!!!!"})
+ with pytest.raises(binascii.Error):
+ _ = session.files.read("/workspace/invalid")
+
+
+def test_sandbox_management_keeps_service_credentials_after_sign_in() -> None:
+ server = SandboxHTTP()
+ _ = server.client.auth.set_session(Session("user-access", "refresh", "user"))
+ server.reply(session_body(), 201)
+ handle = server.client.sandboxes.create(
+ PROJECT, region="aws-us-east-1", preset="python3.12"
+ )
+ for operation in (handle.suspend, handle.resume, handle.terminate):
+ server.reply(session_body(), 202)
+ _ = operation()
+ server.reply(None, 204)
+ server.client.sandboxes.grant(SESSION, SUBJECT, "2026-09-25T00:00:00Z")
+ server.reply(None, 204)
+ server.client.sandboxes.revoke(SESSION, SUBJECT)
+ server.reply(
+ command_body()
+ | {"session_id": SESSION, "region": "aws-us-east-1", "duration_ms": 1}
+ )
+ _ = server.client.sandboxes.exec(
+ PROJECT, "run", region="aws-us-east-1", preset="python3.12"
+ )
+ server.reply({"data": []})
+ _ = server.client.sandboxes.presets()
+ assert {request.headers["Authorization"] for request in server.requests} == {
+ "Bearer service"
+ }
+
+
+def test_sandbox_project_user_cannot_manage_without_service_credentials() -> None:
+ client = VolcanoClient(anon_key="anon")
+ _ = client.auth.set_session(Session("user-access", "refresh", "user"))
+ with pytest.raises(AuthenticationError, match="No service key configured"):
+ _ = client.sandboxes.create(
+ PROJECT, region="aws-us-east-1", preset="python3.12"
+ )
+
+
+def test_sandbox_refresh_keeps_command_identity() -> None:
+ server = SandboxHTTP()
+ _ = server.client.auth.set_session(Session(access_token("old"), "refresh", SUBJECT))
+ server.reply(session_body())
+ handle = server.client.sandboxes.get(SESSION)
+ server.reply({}, 401)
+ server.reply(
+ {
+ "access_token": access_token("new"),
+ "refresh_token": "new-refresh",
+ "token_type": "bearer",
+ "expires_in": 3600,
+ "user": {"id": SUBJECT, "email": "user@example.com", "status": "active"},
+ }
+ )
+ server.reply(command_body())
+ assert handle.exec("run", request_id=KEY).stdout == "hello"
+ assert server.requests[-3].headers["Idempotency-Key"] == KEY
+ assert server.requests[-1].headers["Idempotency-Key"] == KEY
+ assert (
+ server.requests[-1].headers["Authorization"] == f"Bearer {access_token('new')}"
+ )
+ assert len(server.requests) == 4
+
+
+def test_sandbox_granted_operations_keep_user_credentials() -> None:
+ server = SandboxHTTP()
+ _ = server.client.auth.set_session(Session("user-access", "refresh", "user"))
+ server.reply(session_body())
+ handle = server.client.sandboxes.get(SESSION)
+ server.reply(command_body())
+ _ = handle.exec("run")
+ server.reply(None, 204)
+ handle.files.write("/workspace/file", b"hello")
+ server.reply({"data": "aGVsbG8="})
+ assert handle.files.read("/workspace/file") == b"hello"
+ server.reply(
+ {"url": "https://access.test", "token": "secret", "expires_at": "tomorrow"}
+ )
+ _ = handle.access(8080)
+ assert {request.headers["Authorization"] for request in server.requests} == {
+ "Bearer user-access"
+ }
+
+
+def test_sandbox_requires_its_transport_capability() -> None:
+ client = VolcanoClient(
+ anon_key="anon", service_key="service", _transport=RejectingTransport()
+ )
+ with pytest.raises(
+ TypeError, match=r"^Transport does not support Sandbox operations$"
+ ):
+ _ = client.sandboxes.get(SESSION)
+
+
+def test_sandbox_cleanup_preserves_the_body_failure() -> None:
+ server = SandboxHTTP()
+ server.reply(session_body())
+ handle = server.client.sandboxes.get(SESSION)
+ server.reply({"error": "cleanup failed"}, 409)
+ with pytest.raises(ValueError, match="body failed") as caught:
+ fail_inside_session(handle)
+ assert caught.value.__notes__ == ["Sandbox cleanup failed: cleanup failed"]
+ assert server.requests[-1].method == "DELETE"
+ server.reply({"error": "cleanup failed"}, 409)
+ with pytest.raises(ConflictError, match="cleanup failed"), handle:
+ pass
+
+
+class SwitchingSandboxClient(VolcanoClient):
+ def __init__(self, server: SandboxHTTP) -> None:
+ self.switch_next: bool = False
+ super().__init__(
+ anon_key="anon",
+ service_key="service",
+ _transport=GeneratedTransport(
+ api_url="https://sandbox.test",
+ httpx_transport=httpx.MockTransport(server.handle),
+ ),
+ )
+
+ @override
+ def _capture_session_binding(self) -> tuple[int, SessionOperations, Session | None]:
+ binding = super()._capture_session_binding()
+ if self.switch_next:
+ self.switch_next = False
+ _ = self.auth.set_session(Session("new-access", "refresh", "new-user"))
+ return binding
+
+
+def test_sandbox_rejects_a_user_switch_before_dispatch() -> None:
+ server = SandboxHTTP()
+ client = SwitchingSandboxClient(server)
+ _ = client.auth.set_session(Session("old-access", "refresh", "old-user"))
+ client.switch_next = True
+ server.reply(session_body())
+ with pytest.raises(SessionChangedError):
+ _ = client.sandboxes.get(SESSION)
+ assert server.requests == []
diff --git a/src/volcano_sdk/_transport.py b/src/volcano_sdk/_transport.py
index 84b6e23d..a3f346a7 100644
--- a/src/volcano_sdk/_transport.py
+++ b/src/volcano_sdk/_transport.py
@@ -6,6 +6,7 @@
from ._transport_execution import ExecutionTransport
from ._transport_locks import LocksTransport
from ._transport_response import invoke, invoke_async, response_payload
+from ._transport_sandbox import SandboxHTTPTransport
from ._transport_storage import StorageTransport
from ._transport_types import (
ERROR_TYPES_BY_STATUS,
@@ -107,5 +108,6 @@ class GeneratedTransport(
StorageTransport,
ExecutionTransport,
LocksTransport,
+ SandboxHTTPTransport,
):
"""Compose typed generated operations behind the stable SDK transport."""
diff --git a/src/volcano_sdk/_transport_sandbox.py b/src/volcano_sdk/_transport_sandbox.py
new file mode 100644
index 00000000..9f17516a
--- /dev/null
+++ b/src/volcano_sdk/_transport_sandbox.py
@@ -0,0 +1,149 @@
+"""Typed generated Sandbox operation adapters."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING
+from uuid import UUID
+
+import httpx
+
+from ._generated.api.sandboxes.create_sandbox_session import (
+ request_kwargs as create_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.create_sandbox_session_access import (
+ request_kwargs as create_sandbox_session_access_kwargs,
+)
+from ._generated.api.sandboxes.execute_sandbox import (
+ request_kwargs as execute_sandbox_kwargs,
+)
+from ._generated.api.sandboxes.execute_sandbox_session import (
+ request_kwargs as execute_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.get_sandbox_session import (
+ request_kwargs as get_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.grant_sandbox_session import (
+ request_kwargs as grant_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.list_sandbox_presets import (
+ request_kwargs as list_sandbox_presets_kwargs,
+)
+from ._generated.api.sandboxes.read_sandbox_session_file import (
+ request_kwargs as read_sandbox_session_file_kwargs,
+)
+from ._generated.api.sandboxes.resume_sandbox_session import (
+ request_kwargs as resume_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.revoke_sandbox_session import (
+ request_kwargs as revoke_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.suspend_sandbox_session import (
+ request_kwargs as suspend_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.terminate_sandbox_session import (
+ request_kwargs as terminate_sandbox_session_kwargs,
+)
+from ._generated.api.sandboxes.write_sandbox_session_file import (
+ request_kwargs as write_sandbox_session_file_kwargs,
+)
+from ._generated.models.sandbox_access_request import SandboxAccessRequest
+from ._generated.models.sandbox_command_request import SandboxCommandRequest
+from ._generated.models.sandbox_file_read_request import SandboxFileReadRequest
+from ._generated.models.sandbox_file_write_request import SandboxFileWriteRequest
+from ._generated.models.sandbox_subject_grant_request import SandboxSubjectGrantRequest
+from ._transport_base import TransportBase
+from ._transport_response import generated_request, unparsed_response
+from .models import JSONValue
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Mapping
+
+ from ._transport_types import TransportResponse
+
+
+@dataclass(frozen=True)
+class SandboxRequest:
+ """One generated operation with immutable addressing."""
+
+ operation: str
+ resource_id: str = ""
+ subject_id: str = ""
+ body: Mapping[str, JSONValue] = field(default_factory=dict[str, JSONValue])
+ request_id: str = ""
+ timeout: float = 180.0
+
+
+_OPERATIONS: dict[str, Callable[[SandboxRequest], dict[str, object]]] = {
+ "list_sandbox_presets": lambda _request: list_sandbox_presets_kwargs(),
+ "create_sandbox_session": lambda request: create_sandbox_session_kwargs(
+ UUID(request.resource_id),
+ body=dict(request.body),
+ idempotency_key=request.request_id,
+ ),
+ "execute_sandbox": lambda request: execute_sandbox_kwargs(
+ UUID(request.resource_id),
+ body=dict(request.body),
+ idempotency_key=request.request_id,
+ ),
+ "get_sandbox_session": lambda request: get_sandbox_session_kwargs(
+ UUID(request.resource_id)
+ ),
+ "execute_sandbox_session": lambda request: execute_sandbox_session_kwargs(
+ UUID(request.resource_id),
+ body=SandboxCommandRequest.from_dict(dict(request.body)),
+ idempotency_key=request.request_id,
+ ),
+ "suspend_sandbox_session": lambda request: suspend_sandbox_session_kwargs(
+ UUID(request.resource_id)
+ ),
+ "resume_sandbox_session": lambda request: resume_sandbox_session_kwargs(
+ UUID(request.resource_id)
+ ),
+ "terminate_sandbox_session": lambda request: terminate_sandbox_session_kwargs(
+ UUID(request.resource_id)
+ ),
+ "create_sandbox_session_access": lambda request: (
+ create_sandbox_session_access_kwargs(
+ UUID(request.resource_id),
+ body=SandboxAccessRequest.from_dict(dict(request.body)),
+ )
+ ),
+ "read_sandbox_session_file": lambda request: read_sandbox_session_file_kwargs(
+ UUID(request.resource_id),
+ body=SandboxFileReadRequest.from_dict(dict(request.body)),
+ ),
+ "write_sandbox_session_file": lambda request: write_sandbox_session_file_kwargs(
+ UUID(request.resource_id),
+ body=SandboxFileWriteRequest.from_dict(dict(request.body)),
+ ),
+ "grant_sandbox_session": lambda request: grant_sandbox_session_kwargs(
+ UUID(request.resource_id),
+ UUID(request.subject_id),
+ body=SandboxSubjectGrantRequest.from_dict(dict(request.body)),
+ ),
+ "revoke_sandbox_session": lambda request: revoke_sandbox_session_kwargs(
+ UUID(request.resource_id), UUID(request.subject_id)
+ ),
+}
+
+
+class SandboxHTTPTransport(TransportBase):
+ """Dispatch generated Sandbox operations without automatic replay."""
+
+ def sandbox_request(
+ self, *, authorization: str, request: SandboxRequest
+ ) -> TransportResponse:
+ """Return the raw HTTP result for facade validation.
+
+ Returns:
+ The status, headers, and decoded response body.
+
+ """
+ with self._client(authorization).with_timeout(
+ httpx.Timeout(max(self._timeout, request.timeout))
+ ) as client:
+ response = generated_request(
+ client, _OPERATIONS[request.operation](request)
+ )
+ return unparsed_response(response)
diff --git a/src/volcano_sdk/client.py b/src/volcano_sdk/client.py
index af2d0d81..be9761a6 100644
--- a/src/volcano_sdk/client.py
+++ b/src/volcano_sdk/client.py
@@ -12,9 +12,10 @@
from ._auth_requests import AuthRequests
from ._client_context import ClientContext
from ._client_session import BootstrapCredentials, CallbackOutcome, bootstrap_session
+from ._sandbox import SandboxRequests, SandboxTransport
from ._session import validate_refresh_identity
from ._session_operations import SessionOperations
-from ._transport import GeneratedTransport, Transport
+from ._transport import GeneratedTransport, Transport, TransportResponse, invoke
from .auth import Auth, AuthContext
from .database import Database
from .durable import Durable
@@ -30,12 +31,15 @@
Session,
)
from .realtime import CentrifugeFactory, Realtime
+from .sandboxes import Sandboxes
from .storage import Storage
if TYPE_CHECKING:
from _thread import LockType
from collections.abc import Callable, Mapping
+ from ._transport_sandbox import SandboxRequest
+
_NO_ACTIVE_SESSION = "No active session"
_NO_SERVICE_KEY = "No service key configured"
_PROFILE_USER_MISMATCH = "Profile user does not match the active session"
@@ -89,6 +93,7 @@ def __init__(
self.logs: Logs = Logs(self._facades)
self.storage: Storage = Storage(self._facades)
self.locks: Locks = Locks(self._facades)
+ self.sandboxes: Sandboxes = Sandboxes(SandboxRequests(self._sandbox_request))
if _realtime_client_factory is None:
self.realtime: Realtime = Realtime(self._facades, api_url=self._api_url)
else:
@@ -161,6 +166,32 @@ def _service_token(self) -> str:
raise RuntimeError(_NO_SERVICE_KEY)
return self._service_key
+ def _sandbox_request(self, request: SandboxRequest) -> TransportResponse:
+ transport = self._transport
+ if not isinstance(transport, SandboxTransport):
+ message = "Transport does not support Sandbox operations"
+ raise TypeError(message)
+
+ def dispatch(token: str) -> TransportResponse:
+ return invoke(
+ transport.sandbox_request,
+ authorization=token,
+ request=request,
+ )
+
+ binding = self._capture_session_binding()
+ if binding[2] is not None and request.operation in {
+ "get_sandbox_session",
+ "execute_sandbox_session",
+ "read_sandbox_session_file",
+ "write_sandbox_session_file",
+ "create_sandbox_session_access",
+ }:
+ return self._auth_requests.request(dispatch, binding=binding)
+ if self._service_key is None:
+ raise AuthenticationError(_NO_SERVICE_KEY, status=401)
+ return dispatch(self._service_key)
+
def _function_token(self) -> str:
session = self._capture_session()[1]
if session is not None:
diff --git a/src/volcano_sdk/sandbox_models.py b/src/volcano_sdk/sandbox_models.py
new file mode 100644
index 00000000..aec290cf
--- /dev/null
+++ b/src/volcano_sdk/sandbox_models.py
@@ -0,0 +1,69 @@
+"""Public Sandbox options and response values."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import NotRequired, TypedDict
+
+
+class SandboxCreateOptions(TypedDict):
+ """Choose one preset or named Sandbox for a session."""
+
+ region: str
+ preset: NotRequired[str]
+ sandbox_id: NotRequired[str]
+ memory_mb: NotRequired[int]
+ max_duration_seconds: NotRequired[int]
+ idle_timeout_seconds: NotRequired[int]
+ request_id: NotRequired[str]
+
+
+class SandboxCommandOptions(TypedDict, total=False):
+ """Command timeout, environment, and stable retry identity."""
+
+ timeout_seconds: int
+ environment: dict[str, str]
+ request_id: str
+
+
+class SandboxExecOptions(SandboxCreateOptions, SandboxCommandOptions):
+ """One-shot execution selector and command options."""
+
+
+@dataclass(frozen=True)
+class SandboxCommandResult:
+ """Command output, including nonzero exit codes as data."""
+
+ stdout: str
+ stderr: str
+ exit_code: int
+ timed_out: bool
+ stdout_truncated: bool
+ stderr_truncated: bool
+
+
+@dataclass(frozen=True)
+class SandboxExecutionResult(SandboxCommandResult):
+ """One-shot output returned after session reclamation."""
+
+ session_id: str
+ region: str
+ duration_ms: int
+
+
+@dataclass(frozen=True)
+class SandboxAccess:
+ """Expiring HTTP access; the credential is omitted from representations."""
+
+ url: str
+ token: str = field(repr=False)
+ expires_at: str
+
+
+@dataclass(frozen=True)
+class SandboxPreset:
+ """Published preset and its available memory and regions."""
+
+ id: str
+ memory_mb: int
+ regions: tuple[str, ...]
diff --git a/src/volcano_sdk/sandbox_session.py b/src/volcano_sdk/sandbox_session.py
new file mode 100644
index 00000000..7a46c4bd
--- /dev/null
+++ b/src/volcano_sdk/sandbox_session.py
@@ -0,0 +1,194 @@
+"""Stateful Sandbox handle and byte-preserving guest files."""
+
+from __future__ import annotations
+
+import base64
+from typing import TYPE_CHECKING, Self, Unpack
+
+if TYPE_CHECKING:
+ from types import TracebackType
+
+from ._sandbox import (
+ SandboxRequests,
+ command_body,
+ command_result,
+ identifier,
+ record,
+ request_id,
+ state,
+ text,
+)
+from ._transport_sandbox import SandboxRequest
+from .errors import ValidationError
+from .sandbox_models import SandboxAccess, SandboxCommandOptions, SandboxCommandResult
+
+_FILE_LIMIT = 8 * 1024 * 1024
+
+
+class SandboxFiles:
+ """Read and write bytes inside a session."""
+
+ def __init__(self, requests: SandboxRequests, session_id: str) -> None:
+ """Bind files to a validated session identity."""
+ self.requests: SandboxRequests = requests
+ self.session_id: str = session_id
+
+ def read(self, path: str) -> bytes:
+ """Read a guest file without text conversion.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ response = self.requests.send(
+ SandboxRequest(
+ "read_sandbox_session_file", self.session_id, body={"path": path}
+ )
+ )
+ return base64.b64decode(text(record(response).get("data")), validate=True)
+
+ def write(self, path: str, data: bytes) -> None:
+ """Write up to 8 MiB of binary content.
+
+ Raises:
+ ValidationError: If the supplied value violates the Sandbox contract.
+
+ """
+ if len(data) > _FILE_LIMIT:
+ message = "Sandbox files are limited to 8 MiB"
+ raise ValidationError(message)
+ _ = self.requests.send(
+ SandboxRequest(
+ "write_sandbox_session_file",
+ self.session_id,
+ body={"path": path, "data": base64.b64encode(data).decode()},
+ ),
+ 204,
+ )
+
+
+class SandboxSession:
+ """A session handle; context exit requests durable termination."""
+
+ def __init__(self, requests: SandboxRequests, value: object) -> None:
+ """Construct from a validated API response."""
+ data = record(value)
+ self.requests: SandboxRequests = requests
+ self.id: str = identifier(text(data.get("id")))
+ self.project_id: str = identifier(text(data.get("project_id")))
+ self.region: str = text(data.get("region"))
+ self.state: str = state(data.get("state"))
+ self.expires_at: str = text(data.get("expires_at"))
+ self.files: SandboxFiles = SandboxFiles(requests, self.id)
+
+ def refresh(self) -> SandboxSession:
+ """Refresh observed lifecycle state.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ return self._update("get_sandbox_session")
+
+ def suspend(self) -> SandboxSession:
+ """Request suspension while retaining guest files.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ return self._update("suspend_sandbox_session", 202)
+
+ def resume(self) -> SandboxSession:
+ """Request resumption under current admission policy.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ return self._update("resume_sandbox_session", 202)
+
+ def terminate(self) -> SandboxSession:
+ """Request durable termination; completion is asynchronous.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ return self._update("terminate_sandbox_session", 202)
+
+ def _update(self, operation: str, status: int = 200) -> SandboxSession:
+ response = record(
+ self.requests.send(SandboxRequest(operation, self.id), status)
+ )
+ if response.get("id") != self.id:
+ message = "Sandbox session identity changed"
+ raise TypeError(message)
+ next_state = state(response.get("state"))
+ expiry = text(response.get("expires_at"))
+ self.state, self.expires_at = next_state, expiry
+ return self
+
+ def exec(
+ self, command: str, **options: Unpack[SandboxCommandOptions]
+ ) -> SandboxCommandResult:
+ """Execute with a stable retry identity and return nonzero exits as data.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ response = self.requests.send(
+ SandboxRequest(
+ "execute_sandbox_session",
+ self.id,
+ body=command_body(command, options),
+ request_id=request_id(options),
+ timeout=float(options.get("timeout_seconds", 60)) + 120,
+ )
+ )
+ return command_result(response)
+
+ def access(self, port: int) -> SandboxAccess:
+ """Create expiring authenticated HTTP access to a guest port.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ response = record(
+ self.requests.send(
+ SandboxRequest(
+ "create_sandbox_session_access", self.id, body={"port": port}
+ )
+ )
+ )
+ return SandboxAccess(
+ text(response.get("url")),
+ text(response.get("token")),
+ text(response.get("expires_at")),
+ )
+
+ def __enter__(self) -> Self:
+ """Return the owned session.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ return self
+
+ def __exit__(
+ self,
+ _kind: type[BaseException] | None,
+ _error: BaseException | None,
+ _traceback: TracebackType | None,
+ ) -> None:
+ """Request cleanup even if the context body raises."""
+ if self.state != "terminated":
+ try:
+ _ = self.terminate()
+ except Exception as cleanup_error:
+ if _error is None:
+ raise
+ _error.add_note(f"Sandbox cleanup failed: {cleanup_error}")
diff --git a/src/volcano_sdk/sandboxes.py b/src/volcano_sdk/sandboxes.py
new file mode 100644
index 00000000..c3c69b60
--- /dev/null
+++ b/src/volcano_sdk/sandboxes.py
@@ -0,0 +1,155 @@
+"""Sandbox creation, execution, and backend-granted session access."""
+
+from __future__ import annotations
+
+from typing import Unpack, cast
+
+from ._sandbox import (
+ SandboxRequests,
+ command_body,
+ command_result,
+ identifier,
+ integer,
+ record,
+ request_id,
+ selector,
+ text,
+)
+from ._transport_sandbox import SandboxRequest
+from .sandbox_models import (
+ SandboxCreateOptions,
+ SandboxExecOptions,
+ SandboxExecutionResult,
+ SandboxPreset,
+)
+from .sandbox_session import SandboxSession
+
+
+class Sandboxes:
+ """Create isolated sessions or run a command to completion."""
+
+ def __init__(self, requests: SandboxRequests) -> None:
+ """Bind the client's Sandbox request scope."""
+ self.requests: SandboxRequests = requests
+
+ def presets(self) -> tuple[SandboxPreset, ...]:
+ """List the published preset catalog.
+
+ Returns:
+ The validated response or request value.
+
+ Raises:
+ TypeError: If the supplied value violates the Sandbox contract.
+
+ """
+ payload = record(self.requests.send(SandboxRequest("list_sandbox_presets")))
+ rows = payload.get("data")
+ if not isinstance(rows, list):
+ message = "Invalid Sandbox preset catalog"
+ raise TypeError(message)
+ return tuple(_preset(row) for row in cast("list[object]", rows))
+
+ def create(
+ self, project_id: str, **options: Unpack[SandboxCreateOptions]
+ ) -> SandboxSession:
+ """Create a session; preserve request_id when retrying an uncertain result.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ body = selector(options)
+ for key in ("max_duration_seconds", "idle_timeout_seconds"):
+ if key in options:
+ body[key] = integer(options.get(key))
+ response = self.requests.send(
+ SandboxRequest(
+ "create_sandbox_session",
+ identifier(project_id),
+ body=body,
+ request_id=request_id(options),
+ ),
+ 201,
+ )
+ return SandboxSession(self.requests, response)
+
+ def get(self, session_id: str) -> SandboxSession:
+ """Fetch a session visible to the current credential.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ response = self.requests.send(
+ SandboxRequest("get_sandbox_session", identifier(session_id))
+ )
+ return SandboxSession(self.requests, response)
+
+ def exec(
+ self, project_id: str, command: str, **options: Unpack[SandboxExecOptions]
+ ) -> SandboxExecutionResult:
+ """Execute once and return output after confirmed reclamation.
+
+ Returns:
+ The validated response or request value.
+
+ """
+ body = selector(options) | command_body(command, options)
+ result = record(
+ self.requests.send(
+ SandboxRequest(
+ "execute_sandbox",
+ identifier(project_id),
+ body=body,
+ request_id=request_id(options),
+ )
+ )
+ )
+ output = command_result(result)
+ return SandboxExecutionResult(
+ stdout=output.stdout,
+ stderr=output.stderr,
+ exit_code=output.exit_code,
+ timed_out=output.timed_out,
+ stdout_truncated=output.stdout_truncated,
+ stderr_truncated=output.stderr_truncated,
+ session_id=text(result.get("session_id")),
+ region=text(result.get("region")),
+ duration_ms=integer(result.get("duration_ms")),
+ )
+
+ def grant(self, session_id: str, auth_user_id: str, expires_at: str) -> None:
+ """Grant one project auth user access until the specified expiry."""
+ _ = self.requests.send(
+ SandboxRequest(
+ "grant_sandbox_session",
+ identifier(session_id),
+ identifier(auth_user_id),
+ {"expires_at": expires_at},
+ ),
+ 204,
+ )
+
+ def revoke(self, session_id: str, auth_user_id: str) -> None:
+ """Revoke one user's session access."""
+ _ = self.requests.send(
+ SandboxRequest(
+ "revoke_sandbox_session",
+ identifier(session_id),
+ identifier(auth_user_id),
+ ),
+ 204,
+ )
+
+
+def _preset(value: object) -> SandboxPreset:
+ data = record(value)
+ regions = data.get("regions")
+ if not isinstance(regions, list):
+ message = "Invalid Sandbox regions"
+ raise TypeError(message)
+ return SandboxPreset(
+ text(data.get("id")),
+ integer(data.get("memory_mb")),
+ tuple(text(region) for region in cast("list[object]", regions)),
+ )