diff --git a/.env.example b/.env.example index 20ae72159..4344dcd7a 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,7 @@ ### ================================= ### Domain for the E2B cluster. -### Resolves infra-api (`https://api.`) and dashboard-api -### (`https://dashboard-api.`) unless overridden below. +### Resolves infra-api (`https://api.`) unless overridden below. PUBLIC_E2B_DOMAIN=e2b.dev ### ================================= @@ -16,10 +15,9 @@ PUBLIC_E2B_DOMAIN=e2b.dev ### and sign-out is hidden. # E2B_API_KEY=e2b_your_team_api_key -### Runtime API base URLs (override the cluster domain resolution; +### Runtime API base URL (overrides the cluster domain resolution; ### useful for local infra development). # E2B_INFRA_API_URL=http://127.0.0.1:3000 -# E2B_DASHBOARD_API_URL=http://127.0.0.1:3010 ### Base URL the BROWSER uses to reach sandboxes (terminal and filesystem ### inspector). Set this explicitly for local sandbox proxies. It must be diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 71c1aa0de..000000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -spec/openapi.dashboard-api.yaml linguist-generated=true -src/core/shared/contracts/dashboard-api.types.ts linguist-generated=true diff --git a/.github/workflows/sync-infra-specs.yml b/.github/workflows/sync-infra-specs.yml index 4afb8bcfc..69bde0d79 100644 --- a/.github/workflows/sync-infra-specs.yml +++ b/.github/workflows/sync-infra-specs.yml @@ -29,19 +29,18 @@ jobs: with: token: ${{ steps.app-token.outputs.token }} - - name: Download latest infra OpenAPI specs + - name: Download latest infra OpenAPI spec run: | curl -f -L https://raw.githubusercontent.com/e2b-dev/infra/refs/heads/main/spec/openapi.yml -o spec/openapi.infra.yaml - curl -f -L https://raw.githubusercontent.com/e2b-dev/infra/refs/heads/main/spec/openapi-dashboard.yml -o spec/openapi.dashboard-api.yaml - name: Check for spec changes id: check-spec-changes run: | - if [[ -z $(git status --porcelain spec/openapi.infra.yaml spec/openapi.dashboard-api.yaml) ]]; then - echo "No changes detected in infra specs." + if [[ -z $(git status --porcelain spec/openapi.infra.yaml) ]]; then + echo "No changes detected in infra spec." echo "has_changes=false" >> $GITHUB_OUTPUT else - echo "Changes detected in infra specs." + echo "Changes detected in infra spec." echo "has_changes=true" >> $GITHUB_OUTPUT fi @@ -66,14 +65,12 @@ jobs: - name: Regenerate API types if: steps.check-spec-changes.outputs.has_changes == 'true' - run: | - bun run generate:infra - bun run generate:dashboard-api + run: bun run generate:infra - name: Autofix generated type files if: steps.check-spec-changes.outputs.has_changes == 'true' run: | - GENERATED_TYPE_FILES=$(git diff --name-only -- src/core/shared/contracts/infra-api.types.ts src/core/shared/contracts/dashboard-api.types.ts | tr '\n' ' ') + GENERATED_TYPE_FILES=$(git diff --name-only -- src/core/shared/contracts/infra-api.types.ts | tr '\n' ' ') if [[ -z "${GENERATED_TYPE_FILES}" ]]; then echo "No generated type changes detected; skipping autofix." @@ -92,11 +89,9 @@ jobs: commit-message: "chore: sync infra OpenAPI specs" title: "chore: sync infra OpenAPI specs" body: | - This PR syncs OpenAPI specs from [e2b-dev/infra](https://github.com/e2b-dev/infra): - - `spec/openapi.infra.yaml` - - `spec/openapi.dashboard-api.yaml` + This PR syncs `spec/openapi.infra.yaml` from [e2b-dev/infra](https://github.com/e2b-dev/infra). - It also regenerates dependent TypeScript API types and applies formatting/lint autofixes. + It also regenerates the dependent TypeScript API types and applies formatting/lint autofixes. --- *This PR was automatically created by the sync-infra-specs workflow.* diff --git a/README.md b/README.md index 3235a68ae..8c601168e 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,9 @@ Open-source dashboard for self-hosted [E2B infrastructure](https://github.com/e2 ## Overview -Built with Next.js 16, React 19, and TypeScript. The dashboard talks to two APIs from your [E2B infrastructure deployment](https://github.com/e2b-dev/infra): +Built with Next.js 16, React 19, and TypeScript. The dashboard talks to a single API from your [E2B infrastructure deployment](https://github.com/e2b-dev/infra): - **infra-api** (`https://api.`) — sandboxes, templates, builds -- **dashboard-api** (`https://dashboard-api.`) — template & build metadata ### Authentication @@ -32,9 +31,9 @@ Authentication is a single **team API key**: | Variable | Read | Purpose | |---|---|---| -| `PUBLIC_E2B_DOMAIN` | runtime | Required E2B cluster domain; used by the SDK and to derive `https://api.` and `https://dashboard-api.` | +| `PUBLIC_E2B_DOMAIN` | runtime | Required E2B cluster domain; used by the SDK and to derive `https://api.` | | `PUBLIC_SANDBOX_URL` | per request | Optional sandbox traffic base URL, reachable from both the browser and server | -| `E2B_INFRA_API_URL` / `E2B_DASHBOARD_API_URL` | server start | Optional server-side API URLs; override domain-derived URLs | +| `E2B_INFRA_API_URL` | server start | Optional server-side API URL; overrides the domain-derived URL | | `DASHBOARD_COOKIE_SECURE` | server start | `false` only for a plain-http install; the API key cookie then travels unencrypted. Defaults to secure in production builds | Set `PUBLIC_E2B_DOMAIN` when starting the container. Next does not give @@ -50,11 +49,10 @@ set, even if the new name is also present. |---|---| | `NEXT_PUBLIC_E2B_DOMAIN` | `PUBLIC_E2B_DOMAIN` | | `NEXT_PUBLIC_INFRA_API_URL` | `E2B_INFRA_API_URL` | -| `NEXT_PUBLIC_DASHBOARD_API_URL` | `E2B_DASHBOARD_API_URL` | | `NEXT_PUBLIC_E2B_SANDBOX_URL` / `E2B_SANDBOX_URL` | `PUBLIC_SANDBOX_URL` | -API URL overrides remain optional; without them the domain determines both -API URLs. An absent or blank `PUBLIC_SANDBOX_URL` lets the SDK use domain +The API URL override remains optional; without it the domain determines the +API URL. An absent or blank `PUBLIC_SANDBOX_URL` lets the SDK use domain routing. Every API or sandbox URL must include `http://` or `https://`. The schema in @@ -109,8 +107,8 @@ bun install 3. Set up environment variables ```bash cp .env.example .env -# set PUBLIC_E2B_DOMAIN (and optionally E2B_INFRA_API_URL / -# E2B_DASHBOARD_API_URL) to point at your infrastructure +# set PUBLIC_E2B_DOMAIN (and optionally E2B_INFRA_API_URL) to point at +# your infrastructure ``` 4. Start the development server @@ -151,9 +149,8 @@ docker run --rm -p 3001:3001 \ `PUBLIC_E2B_DOMAIN` fails validation. - The build needs outbound HTTPS for the three Google Fonts families in `src/app/fonts.ts`; an air-gapped build fails there. -- `GET /api/health` reports dashboard-api's health and answers 503 while - dashboard-api is unreachable, so use `GET /` as the container liveness - check. +- `GET /api/health` reports infra-api's health and answers 503 while + infra-api is unreachable, so use `GET /` as the container liveness check. - `scripts/container-smoke.sh` builds the image and asserts those responses. ## Scripts @@ -166,7 +163,6 @@ docker run --rm -p 3001:3001 \ | `bun run test:integration` | Integration tests | | `bun run lint` / `bun run format` | Biome lint / format | | `bun run generate:infra` | Regenerate infra-api contract types from `spec/` | -| `bun run generate:dashboard-api` | Regenerate dashboard-api contract types from `spec/` | ## Releases diff --git a/package.json b/package.json index 406e6a8f7..c73ce30a8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "prebuild": "bun scripts:check-app-env", "<<<<<<< Gen": "", "generate:infra": "bunx openapi-typescript ./spec/openapi.infra.yaml -o ./src/core/shared/contracts/infra-api.types.ts", - "generate:dashboard-api": "bunx openapi-typescript ./spec/openapi.dashboard-api.yaml -o ./src/core/shared/contracts/dashboard-api.types.ts", "<<<<<<< Scripts": "", "scripts:check-app-env": "bun scripts/check-app-env.ts", "<<<<<<< Development": "", diff --git a/scripts/container-smoke.sh b/scripts/container-smoke.sh index b07c7af36..d3c5568f3 100755 --- a/scripts/container-smoke.sh +++ b/scripts/container-smoke.sh @@ -55,9 +55,9 @@ check "GET /sandboxes redirects to the key form" 307 \ check "GET /sandboxes redirect target" "http://127.0.0.1:${PORT}/?returnTo=%2Fsandboxes" \ "$(curl -sS -o /dev/null -w '%{redirect_url}' "http://127.0.0.1:${PORT}/sandboxes")" -# /api/health probes dashboard-api, which this run does not provide, so 503 is -# the correct answer here and proves route handlers are being served. -check "GET /api/health without a dashboard-api" 503 \ +# /api/health probes infra-api, which never resolves under smoke.invalid, so 503 +# is the correct answer here and proves route handlers are being served. +check "GET /api/health without an infra-api" 503 \ "$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PORT}/api/health")" if [ "${fail}" != 0 ]; then @@ -91,11 +91,9 @@ check_invalid_config() { check_invalid_config PUBLIC_SANDBOX_URL missing-scheme.example:3002 check_invalid_config PUBLIC_E2B_DOMAIN '' check_invalid_config E2B_INFRA_API_URL ftp://api.example -check_invalid_config E2B_DASHBOARD_API_URL missing-scheme.example:3010 check_invalid_config E2B_SANDBOX_URL https://sandbox.example check_invalid_config NEXT_PUBLIC_E2B_DOMAIN old.example check_invalid_config NEXT_PUBLIC_INFRA_API_URL https://api.old.example -check_invalid_config NEXT_PUBLIC_DASHBOARD_API_URL https://dashboard-api.old.example check_invalid_config NEXT_PUBLIC_E2B_SANDBOX_URL https://sandbox.old.example check_invalid_config DASHBOARD_COOKIE_SECURE off diff --git a/spec/openapi.dashboard-api.yaml b/spec/openapi.dashboard-api.yaml deleted file mode 100644 index 1a9ca745c..000000000 --- a/spec/openapi.dashboard-api.yaml +++ /dev/null @@ -1,2067 +0,0 @@ -openapi: "3.0.0" - -info: - version: 0.1.0 - title: E2B Dashboard API - -components: - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - AdminApiKeyAuth: - type: apiKey - in: header - name: X-Admin-Token - AdminJWTAuth: - type: http - scheme: bearer - bearerFormat: JWT - # AuthProviderBearerAuth / AuthProviderTeamAuth: B before T in the name - # so Bearer is validated before Team. - AuthProviderBearerAuth: - type: http - scheme: bearer - bearerFormat: access_token - AuthProviderTeamAuth: - type: apiKey - in: header - name: X-Team-ID - - parameters: - build_id: - name: build_id - in: path - required: true - description: Identifier of the build. - schema: - type: string - format: uuid - sandboxID: - name: sandboxID - in: path - required: true - description: Identifier of the sandbox. - schema: - type: string - builds_limit: - name: limit - in: query - required: false - description: Maximum number of items to return per page. - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 50 - builds_cursor: - name: cursor - in: query - required: false - description: Cursor returned by the previous list response in `created_at|build_id` format. - schema: - type: string - build_id_or_template: - name: build_id_or_template - in: query - required: false - description: Optional filter by build identifier, template identifier, or template alias. - schema: - type: string - build_statuses: - name: statuses - in: query - required: false - description: Comma-separated list of build statuses to include. - style: form - explode: false - schema: - type: array - items: - $ref: "#/components/schemas/BuildStatus" - build_ids: - name: build_ids - in: query - required: true - description: Comma-separated list of build IDs to get statuses for. - style: form - explode: false - schema: - type: array - items: - type: string - format: uuid - maxItems: 100 - uniqueItems: true - teamID: - name: teamID - in: path - required: true - description: Identifier of the team. - schema: - type: string - format: uuid - clusterID: - name: clusterID - in: path - required: true - description: Identifier of the cluster. - schema: - type: string - format: uuid - projectID: - name: projectID - in: path - required: true - description: Identifier of the project. - schema: - type: string - format: uuid - userID: - name: userID - in: path - required: true - description: Identifier of the user. - schema: - type: string - format: uuid - templates_limit: - name: limit - in: query - required: false - description: Maximum number of items to return per page. - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 50 - templateID: - name: templateID - in: path - required: true - description: Identifier of the template. - schema: - type: string - tag: - name: tag - in: query - required: true - description: Template tag name to check. - schema: - type: string - minLength: 1 - tag_assignment_limit: - name: assignmentLimit - in: query - required: false - description: Maximum number of ready assignment rows to return per tag. - schema: - type: integer - format: int32 - minimum: 1 - maximum: 25 - default: 6 - tag_path: - name: tag - in: path - required: true - description: Template tag name. - schema: - type: string - minLength: 1 - tag_assignments_limit: - name: limit - in: query - required: false - description: Maximum number of assignment rows to return per page. - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 50 - templates_cursor: - name: cursor - in: query - required: false - description: Cursor returned by the previous list response in `{sort}|{value}|{templateID}` format. Rejected if its sort does not match the request. - schema: - type: string - templates_public: - name: public - in: query - required: false - description: Filter templates by visibility (true = public, false = internal). - schema: - type: boolean - templates_search: - name: search - in: query - required: false - description: Case-insensitive substring match on template names, aliases, and template id. - schema: - type: string - templates_sort: - name: sort - in: query - required: false - description: Sort column and direction. - schema: - type: string - enum: [created_at_asc, created_at_desc, updated_at_asc, updated_at_desc] - default: created_at_desc - tag_assignments_cursor: - name: cursor - in: query - required: false - description: Cursor returned by the previous list response in `assigned_at|assignment_id` format. - schema: - type: string - tag_groups_limit: - name: tagsLimit - in: query - required: false - description: Maximum number of distinct tags to return per page. - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 25 - tag_groups_cursor: - name: tagsCursor - in: query - required: false - description: Cursor returned by the previous list response in `{sort}|{latest_assigned_at}|{tag}` format (sort-tagged, RFC3339Nano). - schema: - type: string - tag_groups_search: - name: search - in: query - required: false - description: Case-insensitive substring filter on tag name. Allowed characters are `a-z`, `0-9`, `.`, `_`, `-`. - schema: - type: string - maxLength: 64 - pattern: "^[a-z0-9._-]*$" - tag_groups_sort: - name: sort - in: query - required: false - description: Sort order for the returned tag groups. - schema: - type: string - enum: [latest_desc, latest_asc, name_asc, name_desc] - default: latest_desc - - responses: - "400": - description: Bad request - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Authentication error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "403": - description: Forbidden - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "404": - description: Not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "409": - description: Conflict - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "501": - description: Operation is not implemented - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - schemas: - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - description: Error code. - error_code: - $ref: "#/components/schemas/ErrorCode" - message: - type: string - description: Error message. - - ErrorCode: - type: string - description: >- - Stable machine-readable reason for an API error. New codes may be added - as other errors adopt this contract. Clients must handle absent or unknown - codes without interpreting the human-readable message. - enum: - - cluster_registration_invalid - - cluster_registration_conflict - - cluster_assignment_invalid - - cluster_assignment_project_not_found - - cluster_assignment_cluster_not_found - - cluster_assignment_already_assigned - - cluster_assignment_cluster_deleting - - AdminAuthProviderProfile: - type: object - required: - - userId - - email - properties: - userId: - type: string - format: uuid - description: Internal E2B user identifier. - email: - type: string - nullable: true - description: Email address from the configured auth provider. - - AdminAuthProviderProfilesResponse: - type: object - required: - - profiles - properties: - profiles: - type: array - items: - $ref: "#/components/schemas/AdminAuthProviderProfile" - - AdminAuthProviderProfilesResolveRequest: - type: object - required: - - userIds - properties: - userIds: - type: array - minItems: 1 - maxItems: 100 - uniqueItems: true - items: - type: string - format: uuid - - AdminClusterCreateRequest: - type: object - required: - - name - - endpoint - - endpoint_tls - - token - properties: - cluster_id: - type: string - format: uuid - description: Optional stable identifier for idempotent creation. Reuse succeeds only when the immutable configuration is identical. - name: - type: string - minLength: 1 - endpoint: - type: string - minLength: 1 - endpoint_tls: - type: boolean - token: - type: string - minLength: 1 - sandbox_proxy_domain: - type: string - nullable: true - auth_org_id: - type: string - nullable: true - - AdminClusterCreateResponse: - type: object - required: - - cluster_id - properties: - cluster_id: - type: string - format: uuid - - AdminTeamClusterAssignmentResponse: - type: object - required: - - cluster_id - properties: - cluster_id: - type: string - format: uuid - - BuildStatus: - type: string - description: Build status mapped for dashboard clients. - enum: - - building - - failed - - success - - ListedBuild: - type: object - required: - - id - - template - - templateId - - status - - statusMessage - - createdAt - - finishedAt - - cpuCount - - memoryMB - - diskSizeMB - - envdVersion - properties: - id: - type: string - format: uuid - description: Identifier of the build. - template: - type: string - description: Template alias when present, otherwise template ID. - templateId: - type: string - description: Identifier of the template. - status: - $ref: "#/components/schemas/BuildStatus" - statusMessage: - type: string - nullable: true - description: Failure message when status is `failed`, otherwise `null`. - createdAt: - type: string - format: date-time - description: Build creation timestamp in RFC3339 format. - finishedAt: - type: string - format: date-time - nullable: true - description: Build completion timestamp in RFC3339 format, if finished. - cpuCount: - type: integer - format: int64 - description: CPU cores configured for the build. - memoryMB: - type: integer - format: int64 - description: Memory in MiB configured for the build. - diskSizeMB: - type: integer - format: int64 - nullable: true - description: Disk size in MiB configured for the build, if recorded. - envdVersion: - type: string - nullable: true - description: envd version the build was created with, if recorded. - - BuildsListResponse: - type: object - required: - - data - - nextCursor - properties: - data: - type: array - items: - $ref: "#/components/schemas/ListedBuild" - nextCursor: - type: string - nullable: true - description: Cursor to pass to the next list request, or `null` if there is no next page. - - BuildStatusItem: - type: object - required: - - id - - status - - finishedAt - - statusMessage - properties: - id: - type: string - format: uuid - description: Identifier of the build. - status: - $ref: "#/components/schemas/BuildStatus" - finishedAt: - type: string - format: date-time - nullable: true - description: Build completion timestamp in RFC3339 format, if finished. - statusMessage: - type: string - nullable: true - description: Failure message when status is `failed`, otherwise `null`. - - BuildsStatusesResponse: - type: object - required: - - buildStatuses - properties: - buildStatuses: - type: array - description: List of build statuses - items: - $ref: "#/components/schemas/BuildStatusItem" - - BuildInfo: - type: object - required: - - createdAt - - finishedAt - - status - - statusMessage - properties: - names: - type: array - items: - type: string - nullable: true - description: Template names related to this build, if available. - createdAt: - type: string - format: date-time - description: Build creation timestamp in RFC3339 format. - finishedAt: - type: string - format: date-time - nullable: true - description: Build completion timestamp in RFC3339 format, if finished. - status: - $ref: "#/components/schemas/BuildStatus" - statusMessage: - type: string - nullable: true - description: Failure message when status is `failed`, otherwise `null`. - - CPUCount: - type: integer - format: int64 - minimum: 1 - description: CPU cores for the sandbox - - MemoryMB: - type: integer - format: int64 - minimum: 128 - description: Memory for the sandbox in MiB - - DiskSizeMB: - type: integer - format: int64 - minimum: 0 - description: Disk size for the sandbox in MiB - - SandboxRecord: - type: object - required: - - templateID - - sandboxID - - startedAt - - cpuCount - - memoryMB - - diskSizeMB - - retentionExpired - - eventsRetentionExpired - properties: - templateID: - type: string - description: Identifier of the template from which is the sandbox created - alias: - type: string - description: Alias of the template - sandboxID: - type: string - description: Identifier of the sandbox - startedAt: - type: string - format: date-time - description: Time when the sandbox was started - stoppedAt: - type: string - format: date-time - nullable: true - description: Time when the sandbox was stopped - domain: - type: string - nullable: true - description: Base domain where the sandbox traffic is accessible - cpuCount: - $ref: "#/components/schemas/CPUCount" - memoryMB: - $ref: "#/components/schemas/MemoryMB" - diskSizeMB: - $ref: "#/components/schemas/DiskSizeMB" - retentionExpired: - type: boolean - description: Whether the sandbox ended more than the fixed retention window ago, so its monitoring and logs data is no longer available - eventsRetentionExpired: - type: boolean - description: Whether the sandbox ended more than the team's events retention window ago, so its events data is no longer available - - HealthResponse: - type: object - required: - - message - properties: - message: - type: string - description: Human-readable health check result. - - DefaultTemplateAlias: - type: object - required: - - alias - properties: - alias: - type: string - namespace: - type: string - nullable: true - - DefaultTemplate: - type: object - required: - - id - - aliases - - buildId - - ramMb - - vcpu - - totalDiskSizeMb - - createdAt - - public - - buildCount - - spawnCount - properties: - id: - type: string - aliases: - type: array - items: - $ref: "#/components/schemas/DefaultTemplateAlias" - buildId: - type: string - format: uuid - ramMb: - type: integer - format: int64 - vcpu: - type: integer - format: int64 - totalDiskSizeMb: - type: integer - format: int64 - nullable: true - envdVersion: - type: string - nullable: true - createdAt: - type: string - format: date-time - public: - type: boolean - buildCount: - type: integer - format: int32 - spawnCount: - type: integer - format: int64 - - DefaultTemplatesResponse: - type: object - required: - - templates - properties: - templates: - type: array - items: - $ref: "#/components/schemas/DefaultTemplate" - - TeamTemplate: - type: object - required: - - templateID - - buildID - - cpuCount - - memoryMB - - diskSizeMB - - public - - aliases - - names - - createdAt - - updatedAt - - createdBy - - lastSpawnedAt - - spawnCount - - buildCount - - envdVersion - - isDefault - properties: - templateID: - type: string - buildID: - type: string - format: uuid - cpuCount: - type: integer - format: int64 - memoryMB: - type: integer - format: int64 - diskSizeMB: - type: integer - format: int64 - nullable: true - public: - type: boolean - aliases: - type: array - items: - type: string - names: - type: array - items: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - createdBy: - nullable: true - type: object - required: - - id - properties: - id: - type: string - format: uuid - email: - type: string - nullable: true - lastSpawnedAt: - type: string - format: date-time - nullable: true - spawnCount: - type: integer - format: int64 - buildCount: - type: integer - format: int32 - envdVersion: - type: string - nullable: true - isDefault: - type: boolean - defaultDescription: - type: string - nullable: true - - TeamTemplatesResponse: - type: object - required: - - data - - nextCursor - properties: - data: - type: array - items: - $ref: "#/components/schemas/TeamTemplate" - nextCursor: - type: string - nullable: true - description: Cursor to pass to the next list request, or `null` if there is no next page. - - TemplateDetail: - type: object - description: Dashboard-shaped single-template read. Mirrors the infra-api `Template` schema fields the dashboard renders. - required: - - templateID - - buildID - - cpuCount - - memoryMB - - diskSizeMB - - public - - aliases - - names - - createdAt - - updatedAt - - lastSpawnedAt - - spawnCount - - buildCount - - envdVersion - properties: - templateID: - type: string - description: Identifier of the template. - buildID: - type: string - description: Identifier of the latest ready build for the template, or the zero UUID when none. - cpuCount: - allOf: - - $ref: "#/components/schemas/CPUCount" - nullable: true - description: vCPU count of the latest ready default build, or `null` when none. - memoryMB: - allOf: - - $ref: "#/components/schemas/MemoryMB" - nullable: true - description: Memory in MiB of the latest ready default build, or `null` when none. - diskSizeMB: - allOf: - - $ref: "#/components/schemas/DiskSizeMB" - nullable: true - description: Disk size in MiB of the latest ready default build, or `null` when none. - public: - type: boolean - description: Whether the template is public or only accessible by the team. - aliases: - type: array - description: Aliases of the template. - deprecated: true - items: - type: string - names: - type: array - description: Names of the template (namespace/alias format when namespaced). - items: - type: string - createdAt: - type: string - format: date-time - description: Time when the template was created. - updatedAt: - type: string - format: date-time - description: Time when the template was last updated. - lastSpawnedAt: - type: string - format: date-time - nullable: true - description: Time when the template was last used. - spawnCount: - type: integer - format: int64 - description: Number of times the template was used. - buildCount: - type: integer - format: int32 - description: Number of times the template was built. - envdVersion: - type: string - nullable: true - description: envd version of the latest ready default build, or `null` when none. - - TemplateTagAssignment: - type: object - required: - - assignmentId - - buildId - - assignedAt - - buildCreatedAt - - buildFinishedAt - properties: - assignmentId: - type: string - format: uuid - description: Identifier of the tag assignment event. - buildId: - type: string - format: uuid - description: Identifier of the assigned build. - assignedAt: - type: string - format: date-time - description: Time when the tag was assigned to the build. - buildCreatedAt: - type: string - format: date-time - description: Time when the assigned build was created. - buildFinishedAt: - type: string - format: date-time - nullable: true - description: Time when the assigned build finished. - - TemplateTagGroup: - type: object - required: - - tag - - assignments - - hasMore - properties: - tag: - type: string - description: Template tag name. - assignments: - type: array - description: Ready assignment events for this tag, sorted latest first. - items: - $ref: "#/components/schemas/TemplateTagAssignment" - hasMore: - type: boolean - description: Whether more ready assignment events exist beyond the requested assignment limit. - - TemplateTagGroupsResponse: - type: object - required: - - tags - - nextCursor - properties: - tags: - type: array - items: - $ref: "#/components/schemas/TemplateTagGroup" - nextCursor: - type: string - nullable: true - description: Cursor to pass as `tagsCursor` for the next page, or `null` if there is no next page. - - TemplateTagsCountResponse: - type: object - required: - - total - properties: - total: - type: integer - format: int64 - description: Total distinct ready tags for the template. - - TemplateTagExistsResponse: - type: object - required: - - exists - - normalizedTag - properties: - exists: - type: boolean - description: Whether the template tag has at least one ready assignment. - normalizedTag: - type: string - description: Normalized template tag name. - - TemplateTagAssignmentsResponse: - type: object - required: - - data - - nextCursor - properties: - data: - type: array - description: Ready assignment events for the tag, sorted latest first. - items: - $ref: "#/components/schemas/TemplateTagAssignment" - nextCursor: - type: string - nullable: true - description: Cursor to pass to the next list request, or `null` if there is no next page. - - ManagementProjectUpsertRequest: - type: object - description: >- - The properties of a project this side stores. Name, slug and email are - synchronized on every push. The optional cluster assignment is preserved - when omitted. - - - A project's tier is not among them. It is assigned once, at creation, - from this side's own default, and no push moves it — limits arrive - separately and in full through upsertProjectLimits, and none is - resolved from the tier. - required: [name, slug, email] - properties: - cluster_id: - type: string - format: uuid - description: Cluster assignment to synchronize. Omission preserves the existing assignment. - name: - type: string - minLength: 1 - maxLength: 255 - slug: - type: string - minLength: 1 - maxLength: 63 - description: >- - Changing it renames the project, and its template names follow: - each name held under an earlier slug is also given under the new - one, and the old names keep working. A name the new slug already - holds is left as it is. A slug already held on this control plane - is a 409, on a rename as much as on a create. - email: - type: string - minLength: 1 - maxLength: 255 - description: Contact address recorded on the project. - - ManagementProject: - allOf: - - $ref: "#/components/schemas/ManagementProjectUpsertRequest" - - type: object - required: [id] - properties: - id: - type: string - format: uuid - - ManagementClusterProjects: - type: object - required: [project_ids] - properties: - project_ids: - type: array - items: - type: string - format: uuid - - ManagementClusterRegistrationRequest: - type: object - required: - - name - - endpoint - - endpoint_tls - - token - properties: - name: - type: string - minLength: 1 - endpoint: - type: string - minLength: 1 - endpoint_tls: - type: boolean - token: - type: string - minLength: 1 - sandbox_proxy_domain: - type: string - nullable: true - auth_org_id: - type: string - nullable: true - - ManagementProjectMemberIdentity: - type: object - required: [issuer, subject] - properties: - issuer: - type: string - minLength: 1 - maxLength: 2048 - subject: - type: string - minLength: 1 - maxLength: 2048 - - ManagementProjectMemberApplyRequest: - type: object - required: [revision, present] - properties: - revision: - type: integer - format: int64 - minimum: 1 - present: - type: boolean - is_default: - type: boolean - default: false - description: Whether this membership is the user's default team. Omitted requests from older sources remain non-default. - identities: - type: array - maxItems: 16 - items: - $ref: "#/components/schemas/ManagementProjectMemberIdentity" - - ManagementProjectBlockRequest: - type: object - description: >- - Desired project block state. A newer revision replaces the state, - including any intervening manual block or unblock. - required: [revision, blocked] - properties: - revision: - type: integer - format: int64 - minimum: 1 - description: >- - Monotonic per-project revision. Older or duplicate revisions - succeed without changing stored state. - blocked: - type: boolean - description: Whether the project is refused billable work. - reason: - type: string - description: >- - Reason included in blocked-request errors. Cleared when blocked is false. - decided_at: - $ref: "#/components/schemas/ManagementDecidedAt" - - ManagementDecidedAt: - type: string - format: date-time - description: >- - When the caller decided this revision, in RFC 3339. Stored with the - revision and used to measure how long the decision took to apply here. - Omit it when the caller does not know; the delivery is then counted as - origin unknown rather than as zero lag. - - ManagementProjectLimits: - type: object - description: >- - A project's effective limits, already resolved by the caller. Every - field is absolute: this side stores what it is given and performs no - arithmetic of its own. - - - The minimums below are the contract for what a limit may be, at least - as strict as the CHECK constraints on default_limits. project_limits - stores the same values under looser constraints on purpose — it is a - push target, and a floor that only rejects the impossible keeps a - future decision about what is allowed a change to this schema rather - than a migration. - - - `max_disk_size_mb` and `max_free_disk_size_mb` are one ceiling under two - names, and either name alone carries it. A caller that sends both must - send them equal; a caller that sends neither is refused. Sending both is - what a caller does while receivers older than the second name are still - running. - required: - - revision - - concurrent_sandboxes - - max_sandbox_length_hours - - max_vcpu - - max_ram_mb - - disk_mb - - concurrent_template_builds - - events_ttl_days - - default_free_disk_size_mb - - api_team_rps_list - - api_team_rps_delete - - max_secrets - anyOf: - - required: [max_free_disk_size_mb] - - required: [max_disk_size_mb] - properties: - revision: - type: integer - format: int64 - minimum: 1 - description: >- - The caller's version of this answer, raised whenever the limits it - resolved for the project change. Delivery is over a network, so two - pushes can be in flight at once and arrive in either order: this - side stores the revision it accepted and drops a delivery at or - below it, which is what keeps a delayed retry from putting the - project back on limits it has already left. - - - Comparable only against earlier revisions for the same project. - concurrent_sandboxes: - type: integer - format: int32 - minimum: 1 - max_sandbox_length_hours: - type: integer - format: int32 - minimum: 1 - max_vcpu: - type: integer - format: int32 - minimum: 1 - max_ram_mb: - type: integer - format: int64 - minimum: 1 - disk_mb: - type: integer - format: int64 - minimum: 1 - concurrent_template_builds: - type: integer - format: int32 - minimum: 1 - events_ttl_days: - type: integer - format: int32 - minimum: 1 - default_free_disk_size_mb: - type: integer - format: int64 - minimum: 0 - description: >- - The default free-space growth target when a template build request - omits one. May sit anywhere at or below the maximum free-space - growth target, and usually sits well below it; a delivery whose - default exceeds the maximum is rejected. - max_free_disk_size_mb: - type: integer - format: int64 - minimum: 1 - description: >- - The most a template build may request as its free-space growth - target. - max_disk_size_mb: - type: integer - format: int64 - minimum: 1 - description: >- - The same ceiling as max_free_disk_size_mb, under the name it was - first published with. Kept until every deployed sender and receiver - speaks the other name. - api_team_rps_list: - type: integer - format: int64 - minimum: 0 - description: >- - Requests per second a project may make to each list endpoint. Zero - disables the limit. - api_team_rps_delete: - type: integer - format: int64 - minimum: 0 - description: >- - Requests per second a project may make to each delete endpoint. Zero - disables the limit. - max_secrets: - type: integer - format: int32 - minimum: 1 - description: Live secrets the project may hold. - decided_at: - $ref: "#/components/schemas/ManagementDecidedAt" - -tags: - - name: builds - - name: control-plane-management - description: Workspace control-plane operations authenticated with service JWTs. - - name: sandboxes - - name: templates - -paths: - /health: - get: - summary: Health check - responses: - "200": - description: Health check successful - content: - application/json: - schema: - $ref: "#/components/schemas/HealthResponse" - - /builds: - get: - summary: List team builds - tags: [builds] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/build_id_or_template" - - $ref: "#/components/parameters/build_statuses" - - $ref: "#/components/parameters/builds_limit" - - $ref: "#/components/parameters/builds_cursor" - responses: - "200": - description: Successfully returned paginated builds. - content: - application/json: - schema: - $ref: "#/components/schemas/BuildsListResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "500": - $ref: "#/components/responses/500" - - /builds/statuses: - get: - summary: Build statuses - tags: [builds] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/build_ids" - - responses: - "200": - description: Successfully returned build statuses - content: - application/json: - schema: - $ref: "#/components/schemas/BuildsStatusesResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "500": - $ref: "#/components/responses/500" - - /builds/{build_id}: - get: - summary: Build details - tags: [builds] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/build_id" - responses: - "200": - description: Successfully returned build details. - content: - application/json: - schema: - $ref: "#/components/schemas/BuildInfo" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /sandboxes/{sandboxID}/record: - get: - summary: Sandbox record - tags: [sandboxes] - security: - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/sandboxID" - responses: - "200": - description: Successfully returned sandbox details. - content: - application/json: - schema: - $ref: "#/components/schemas/SandboxRecord" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /admin/clusters: - post: - summary: Create a cluster - description: Creates a cluster whose configuration cannot be modified. The cluster starts with deletion protection turned on. - tags: [admin] - security: - - AdminApiKeyAuth: [] - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AdminClusterCreateRequest" - responses: - "201": - description: Cluster created. - content: - application/json: - schema: - $ref: "#/components/schemas/AdminClusterCreateResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - - /admin/clusters/{clusterID}: - delete: - summary: Delete a cluster and its environments - description: Deletes a cluster after all team assignments are detached. In the same transaction, soft-deletes every live template, snapshot template and paused sandbox snapshot on the cluster, releases their aliases, and releases all environment references while preserving environment and build history. A team assignment rejects the deletion and leaves the environments untouched, and so does deletion protection on the cluster. The local cluster's nil ID is rejected. Repeating a completed deletion succeeds. - tags: [admin] - security: - - AdminApiKeyAuth: [] - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/clusterID" - responses: - "204": - description: Cluster deleted or already absent. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - - /v1/management/clusters/{clusterID}/projects: - get: - operationId: managementListClusterProjects - summary: List the projects assigned to a cluster (v1). - description: Returns the ID of every project whose team is assigned to the cluster, whichever way it was assigned. A deleted project is never assigned. An absent cluster has no projects. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/clusterID" - responses: - "200": - description: The projects assigned to the cluster. - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementClusterProjects" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /v1/management/clusters/{clusterID}/deletion: - put: - operationId: managementRequestClusterDeletion - summary: Record that a cluster is being deleted (v1). - description: Records that the caller is deleting the cluster, for a caller that deletes the cluster's teams before the cluster itself. Refused while the cluster has deletion protection turned on; a protection change in flight commits before or after the check, never between. Once recorded, deletion protection cannot be turned on, so the cluster delete that follows is not refused for protection, and no team can be newly assigned to the cluster. An assignment either commits before the deletion is recorded, where the caller's next project listing finds it, or is refused with cluster_assignment_cluster_deleting. Succeeds when the cluster is absent. Repeating the request succeeds. The local cluster's nil ID is rejected. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/clusterID" - responses: - "204": - description: The deletion is recorded, or the cluster is absent. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - description: The cluster has deletion protection turned on. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - $ref: "#/components/responses/500" - - /v1/management/clusters/{clusterID}/destroy-readiness: - get: - operationId: managementClusterDestroyReadiness - summary: Check cluster destroy readiness - description: Checks whether the cluster has deletion protection turned on, or more than one team uses this exact cluster, either as an assigned team or as the owner of active templates or snapshots on it. One team's own templates and snapshots do not block this check, because deleting the cluster soft-deletes them. Soft-deleted history does not count. Returns success if the cluster is absent. This read does not change resources or prevent later assignments or template creation. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/clusterID" - responses: - "204": - description: At most one team uses the cluster. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - description: The cluster has deletion protection turned on, or more than one team uses it and the other teams and their templates must move off it before destroying the cluster. - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - $ref: "#/components/responses/500" - - /admin/teams/{teamID}/cluster: - get: - summary: Get a team's assigned cluster - description: Returns the current cluster assignment without exposing cluster credentials. - tags: [admin] - security: - - AdminApiKeyAuth: [] - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/teamID" - responses: - "200": - description: Cluster assignment returned. - content: - application/json: - schema: - $ref: "#/components/schemas/AdminTeamClusterAssignmentResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - /admin/teams/{teamID}/ban: - put: - summary: Ban a team - description: Marks the team as banned so its API keys stop authenticating. Idempotent; running workloads are not touched. - tags: [admin] - security: - - AdminApiKeyAuth: [] - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/teamID" - responses: - "204": - description: Team banned. - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - delete: - summary: Unban a team - description: Clears the team's ban. Idempotent. - tags: [admin] - security: - - AdminApiKeyAuth: [] - - AdminJWTAuth: [] - parameters: - - $ref: "#/components/parameters/teamID" - responses: - "204": - description: Team unbanned. - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /admin/user-profiles/resolve: - post: - summary: Resolve user profiles - tags: [admin] - security: - - AdminApiKeyAuth: [] - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AdminAuthProviderProfilesResolveRequest" - responses: - "200": - description: Successfully resolved profiles. - content: - application/json: - schema: - $ref: "#/components/schemas/AdminAuthProviderProfilesResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /templates: - get: - summary: List team templates - description: Returns a paginated list of the team's templates (and default templates inline, unless the team is on a dedicated cluster), with filtering, search, and column sorting via keyset cursor pagination. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templates_public" - - $ref: "#/components/parameters/templates_search" - - $ref: "#/components/parameters/templates_sort" - - $ref: "#/components/parameters/templates_limit" - - $ref: "#/components/parameters/templates_cursor" - responses: - "200": - description: Successfully returned paginated templates. - content: - application/json: - schema: - $ref: "#/components/schemas/TeamTemplatesResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "500": - $ref: "#/components/responses/500" - - /templates/defaults: - get: - summary: List default templates - description: Returns the list of default templates with their latest build info and aliases. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - responses: - "200": - description: Successfully returned default templates. - content: - application/json: - schema: - $ref: "#/components/schemas/DefaultTemplatesResponse" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /templates/{templateID}: - get: - summary: Template - description: Returns a single template owned by the current team. Dashboard-shaped read, indexed by template ID. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - responses: - "200": - description: Successfully returned the template. - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateDetail" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /templates/{templateID}/tags/groups: - get: - summary: List template tag groups - description: Returns ready template tag assignment groups with bounded per-tag history, paginated over tags with keyset cursor. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - - $ref: "#/components/parameters/tag_assignment_limit" - - $ref: "#/components/parameters/tag_groups_limit" - - $ref: "#/components/parameters/tag_groups_cursor" - - $ref: "#/components/parameters/tag_groups_search" - - $ref: "#/components/parameters/tag_groups_sort" - responses: - "200": - description: Successfully returned template tag groups. - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateTagGroupsResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /templates/{templateID}/tags/count: - get: - summary: Count template tags - description: Returns the total number of distinct ready tags for the template. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - responses: - "200": - description: Successfully returned tag count. - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateTagsCountResponse" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /templates/{templateID}/tags/exists: - get: - summary: Check ready template tag existence - description: Checks whether a template tag has at least one ready assignment. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - - $ref: "#/components/parameters/tag" - responses: - "200": - description: Successfully checked template tag existence. - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateTagExistsResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /templates/{templateID}/tags/{tag}/assignments: - get: - summary: List ready assignments for a single template tag - description: Returns ready tag assignment events for a single tag, ordered newest first, with keyset cursor pagination. - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - - $ref: "#/components/parameters/tag_path" - - $ref: "#/components/parameters/tag_assignments_cursor" - - $ref: "#/components/parameters/tag_assignments_limit" - responses: - "200": - description: Successfully returned tag assignment page. - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateTagAssignmentsResponse" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "403": - $ref: "#/components/responses/403" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /v1/management/projects/{projectID}: - parameters: - - $ref: "#/components/parameters/projectID" - put: - operationId: managementUpsertProject - summary: Create or reconcile a project (v1). - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementProjectUpsertRequest" - responses: - "200": - description: Existing project reconciled. - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementProject" - "201": - description: Project created. - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementProject" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - "501": - $ref: "#/components/responses/501" - delete: - operationId: managementDeleteProject - summary: Soft-delete a project (v1). - description: >- - Marks the project deleted and blocked, releases its cluster assignment, - scrubs its name and email to DELETED and its slug to - deleted-, which releases the old slug for reuse, deletes - its memberships and API keys, soft-deletes its templates and - snapshots, and enqueues the teardown of its running sandboxes, all in - one transaction. The project row remains. Afterwards the project - answers 404 to other management writes. Repeating the call answers - 204 and repeats every step except the deletion time: it re-blocks and - re-scrubs the project, clears a cluster assignment written since, - deletes any memberships, keys, templates or snapshots created since, - and enqueues the teardown again. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - responses: - "204": - description: Project is deleted and its teardown is enqueued. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - - /v1/management/projects/{projectID}/members/{userID}: - parameters: - - $ref: "#/components/parameters/projectID" - - $ref: "#/components/parameters/userID" - put: - operationId: managementApplyProjectMember - summary: Apply one versioned project member projection (v1). - description: >- - Applies the newest desired presence for one project member. An older - or duplicate revision is accepted without changing target state. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementProjectMemberApplyRequest" - responses: - "204": - description: Membership projection is applied or already superseded. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - - /v1/management/projects/{projectID}/limits: - parameters: - - $ref: "#/components/parameters/projectID" - put: - operationId: managementUpsertProjectLimits - summary: Reconcile effective limits for a project (v1). - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementProjectLimits" - responses: - "204": - description: Effective limits are synchronized. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - "501": - $ref: "#/components/responses/501" - - /v1/management/projects/{projectID}/block: - parameters: - - $ref: "#/components/parameters/projectID" - put: - operationId: managementApplyProjectBlock - summary: Apply a project's block state (v1). - description: >- - Records whether the project is refused billable work, which the auth - path reads before it lets a sandbox start. An older or duplicate - revision is accepted without changing target state. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementProjectBlockRequest" - responses: - "204": - description: Block state is applied or already superseded. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "500": - $ref: "#/components/responses/500" - "501": - $ref: "#/components/responses/501" - - /v1/management/clusters/{clusterID}: - parameters: - - $ref: "#/components/parameters/clusterID" - put: - operationId: managementRegisterCluster - summary: Register a cluster (v1). - description: A cluster registered here for the first time starts with deletion protection turned off. Registering an existing cluster again with the same name and auth organization updates its endpoint, TLS setting, sandbox proxy domain and token and keeps its stored deletion protection; a different name or auth organization returns a conflict. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/ManagementClusterRegistrationRequest" - responses: - "204": - description: Cluster registration is present. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - delete: - operationId: managementDeleteCluster - summary: Delete a cluster and its environments (v1). - description: Deletes a cluster after all team assignments are detached. In the same transaction, soft-deletes every live template, snapshot template and paused sandbox snapshot on the cluster, releases their aliases, and releases all environment references while preserving environment and build history. A team assignment rejects the deletion and leaves the environments untouched, and so does deletion protection on the cluster. The local cluster's nil ID is rejected. Repeating a completed deletion succeeds. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - responses: - "204": - description: Cluster is absent. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - - /v1/management/projects/{projectID}/cluster/{clusterID}: - parameters: - - $ref: "#/components/parameters/projectID" - - $ref: "#/components/parameters/clusterID" - put: - operationId: managementAssignProjectCluster - summary: Assign a cluster to a project (v1). - description: >- - Assigns the cluster when the project has no cluster assigned. Replaying the identical - assignment succeeds; replacing a different assigned cluster returns a conflict. - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - responses: - "204": - description: Cluster is assigned to the project. - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" - delete: - operationId: managementDetachProjectCluster - summary: Detach a cluster assignment from a project (v1). - tags: [control-plane-management] - security: - - AdminJWTAuth: [] - responses: - "204": - description: The matching assignment is absent. - "401": - $ref: "#/components/responses/401" - "404": - $ref: "#/components/responses/404" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts index c7b9e2645..52a466a9d 100644 --- a/src/app/api/health/route.ts +++ b/src/app/api/health/route.ts @@ -1,30 +1,30 @@ import { NextResponse } from 'next/server' -import { api } from '@/core/shared/clients/api' +import { infra } from '@/core/shared/clients/api' import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' export const maxDuration = 10 -async function checkDashboardApi(): Promise { +async function checkInfraApi(): Promise { try { - const { error } = await api.GET('/health', {}) + const { error } = await infra.GET('/health', {}) if (!error) { return true } l.error( { - key: 'health_check:dashboard_api_error', + key: 'health_check:infra_api_error', error, }, - 'Dashboard API health check failed' + 'Infra API health check failed' ) } catch (error) { l.error( { - key: 'health_check:dashboard_api_error', + key: 'health_check:infra_api_error', error: serializeErrorForLog(error), }, - 'Dashboard API health check failed' + 'Infra API health check failed' ) } @@ -32,17 +32,17 @@ async function checkDashboardApi(): Promise { } export async function GET() { - const dashboardApi = await checkDashboardApi() + const infraApi = await checkInfraApi() - const checks = { dashboardApi } + const checks = { infraApi } return NextResponse.json( { - status: dashboardApi ? 'ok' : 'degraded', + status: infraApi ? 'ok' : 'degraded', checks, }, { - status: dashboardApi ? 200 : 503, + status: infraApi ? 200 : 503, headers: { // vercel infra respects this to cache on cdn 'Cache-Control': 'public, max-age=30, must-revalidate', diff --git a/src/configs/cookies.ts b/src/configs/cookies.ts index c6d237fbf..ccb9df3df 100644 --- a/src/configs/cookies.ts +++ b/src/configs/cookies.ts @@ -6,7 +6,7 @@ import { serverSchema } from '@/lib/env' * Organized by functionality for better maintainability. */ export const COOKIE_KEYS = { - // httpOnly; holds the team API key used against infra-api / dashboard-api. + // httpOnly; holds the team API key used against infra-api. API_KEY: 'e2b_api_key', SIDEBAR_STATE: 'e2b-sidebar-state', diff --git a/src/core/modules/sandboxes/models.ts b/src/core/modules/sandboxes/models.ts index ef7d84dc6..c494793cd 100644 --- a/src/core/modules/sandboxes/models.ts +++ b/src/core/modules/sandboxes/models.ts @@ -1,4 +1,3 @@ -import type { components as DashboardComponents } from '@/contracts/dashboard-api' import type { components as InfraComponents } from '@/contracts/infra-api' export type SandboxLogLevel = InfraComponents['schemas']['LogLevel'] @@ -98,23 +97,3 @@ export function mapInfraSandboxDetailsToModel( state: sandbox.state, } } - -export function mapApiSandboxRecordToModel( - sandbox: DashboardComponents['schemas']['SandboxRecord'] -): KilledSandboxDetailsModel { - const stoppedAt = sandbox.stoppedAt ?? null - - return { - templateID: sandbox.templateID, - alias: sandbox.alias, - sandboxID: sandbox.sandboxID, - startedAt: sandbox.startedAt, - endAt: stoppedAt, - stoppedAt, - domain: sandbox.domain, - cpuCount: sandbox.cpuCount, - memoryMB: sandbox.memoryMB, - diskSizeMB: sandbox.diskSizeMB, - state: 'killed', - } -} diff --git a/src/core/modules/sandboxes/repository.server.ts b/src/core/modules/sandboxes/repository.server.ts index acba17b78..01a2f38e9 100644 --- a/src/core/modules/sandboxes/repository.server.ts +++ b/src/core/modules/sandboxes/repository.server.ts @@ -1,21 +1,19 @@ import 'server-only' import { apiKeyHeaders } from '@/configs/api' -import type { components as DashboardComponents } from '@/contracts/dashboard-api' import type { components as InfraComponents } from '@/contracts/infra-api' import type { Sandboxes, SandboxesMetricsRecord, SandboxState, } from '@/core/modules/sandboxes/models' -import { api, infra } from '@/core/shared/clients/api' +import { infra } from '@/core/shared/clients/api' import { l } from '@/core/shared/clients/logger/logger' import { repoErrorFromHttp } from '@/core/shared/errors' import type { RequestScope } from '@/core/shared/repository-scope' import { err, ok, type RepoResult } from '@/core/shared/result' type SandboxesRepositoryDeps = { - apiClient: typeof api infraClient: typeof infra apiKeyHeaders: typeof apiKeyHeaders } @@ -54,16 +52,10 @@ export interface SandboxesRepository { options?: GetSandboxLogsOptions ): Promise> getSandboxDetails(sandboxId: string): Promise< - RepoResult< - | { - source: 'infra' - details: InfraComponents['schemas']['SandboxDetail'] - } - | { - source: 'database-record' - details: DashboardComponents['schemas']['SandboxRecord'] - } - > + RepoResult<{ + source: 'infra' + details: InfraComponents['schemas']['SandboxDetail'] + }> > getSandboxMetrics( sandboxId: string, @@ -83,7 +75,6 @@ const SANDBOX_NOT_FOUND_MESSAGE = export function createSandboxesRepository( scope: SandboxesRequestScope, deps: SandboxesRepositoryDeps = { - apiClient: api, infraClient: infra, apiKeyHeaders: apiKeyHeaders, } @@ -181,10 +172,6 @@ export function createSandboxesRepository( ) } - // OSS: the dashboard-api killed-sandbox archival record endpoint - // (`GET /sandboxes/{sandboxID}/record`) is not available; we assume a - // 404 without calling it. The `database-record` source and its models - // are kept for parity with console. return err(repoErrorFromHttp(404, SANDBOX_NOT_FOUND_MESSAGE)) }, async getSandboxMetrics(sandboxId, options) { diff --git a/src/core/server/api/routers/sandbox.ts b/src/core/server/api/routers/sandbox.ts index 3f302b12a..0ee8ee3d7 100644 --- a/src/core/server/api/routers/sandbox.ts +++ b/src/core/server/api/routers/sandbox.ts @@ -3,7 +3,6 @@ import { millisecondsInDay } from 'date-fns/constants' import { Sandbox, TimeoutError } from 'e2b' import { z } from 'zod' import { - mapApiSandboxRecordToModel, mapInfraSandboxDetailsToModel, mapInfraSandboxLogToModel, type SandboxDetailsModel, @@ -53,10 +52,9 @@ export const sandboxRouter = createTRPCRouter({ throwTRPCErrorFromRepoError(detailsResult.error) } - const mappedDetails: SandboxDetailsModel = - detailsResult.data.source === 'infra' - ? mapInfraSandboxDetailsToModel(detailsResult.data.details) - : mapApiSandboxRecordToModel(detailsResult.data.details) + const mappedDetails: SandboxDetailsModel = mapInfraSandboxDetailsToModel( + detailsResult.data.details + ) // OSS: lifecycle events (argus) are unavailable; derive the lifecycle // timeline from the sandbox details alone. Kept shape-identical to diff --git a/src/core/server/runtime-config.ts b/src/core/server/runtime-config.ts index 0b0f1923b..0053f5c0d 100644 --- a/src/core/server/runtime-config.ts +++ b/src/core/server/runtime-config.ts @@ -11,14 +11,6 @@ export function resolveInfraApiUrl(): string { return env.E2B_INFRA_API_URL ?? `https://api.${env.PUBLIC_E2B_DOMAIN}` } -export function resolveDashboardApiUrl(): string { - const env = runtimeEnvSchema.parse(process.env) - return ( - env.E2B_DASHBOARD_API_URL ?? - `https://dashboard-api.${env.PUBLIC_E2B_DOMAIN}` - ) -} - export function resolveSandboxUrl(): string | undefined { return runtimeEnvSchema.parse(process.env).PUBLIC_SANDBOX_URL } diff --git a/src/core/shared/clients/api.ts b/src/core/shared/clients/api.ts index 54146b697..3f6e3783a 100644 --- a/src/core/shared/clients/api.ts +++ b/src/core/shared/clients/api.ts @@ -1,18 +1,10 @@ import createClient from 'openapi-fetch' -import { - resolveDashboardApiUrl, - resolveInfraApiUrl, -} from '@/core/server/runtime-config' -import type { paths as DashboardPaths } from '@/core/shared/contracts/dashboard-api.types' +import { resolveInfraApiUrl } from '@/core/server/runtime-config' import type { paths as InfraPaths } from '@/core/shared/contracts/infra-api.types' -type CombinedPaths = InfraPaths - const INFRA_API_URL = resolveInfraApiUrl() -const DASHBOARD_API_URL = resolveDashboardApiUrl() - -export const infra = createClient({ +export const infra = createClient({ baseUrl: INFRA_API_URL, fetch: ({ url, headers, body, method, ...options }) => { return fetch(url, { @@ -27,19 +19,3 @@ export const infra = createClient({ array: { style: 'form', explode: false }, }, }) - -export const api = createClient({ - baseUrl: DASHBOARD_API_URL, - fetch: ({ url, headers, body, method, ...options }) => { - return fetch(url, { - headers, - body, - method, - duplex: body ? 'half' : undefined, - ...options, - } as RequestInit) - }, - querySerializer: { - array: { style: 'form', explode: false }, - }, -}) diff --git a/src/core/shared/contracts/dashboard-api.types.ts b/src/core/shared/contracts/dashboard-api.types.ts deleted file mode 100644 index 8f18ff6fe..000000000 --- a/src/core/shared/contracts/dashboard-api.types.ts +++ /dev/null @@ -1,2011 +0,0 @@ -/** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. - */ - -export interface paths { - '/health': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Health check */ - get: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Health check successful */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['HealthResponse'] - } - } - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/builds': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** List team builds */ - get: { - parameters: { - query?: { - /** @description Optional filter by build identifier, template identifier, or template alias. */ - build_id_or_template?: components['parameters']['build_id_or_template'] - /** @description Comma-separated list of build statuses to include. */ - statuses?: components['parameters']['build_statuses'] - /** @description Maximum number of items to return per page. */ - limit?: components['parameters']['builds_limit'] - /** @description Cursor returned by the previous list response in `created_at|build_id` format. */ - cursor?: components['parameters']['builds_cursor'] - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned paginated builds. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['BuildsListResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 403: components['responses']['403'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/builds/statuses': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Build statuses */ - get: { - parameters: { - query: { - /** @description Comma-separated list of build IDs to get statuses for. */ - build_ids: components['parameters']['build_ids'] - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned build statuses */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['BuildsStatusesResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 403: components['responses']['403'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/builds/{build_id}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Build details */ - get: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the build. */ - build_id: components['parameters']['build_id'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned build details. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['BuildInfo'] - } - } - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/sandboxes/{sandboxID}/record': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Sandbox record */ - get: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the sandbox. */ - sandboxID: components['parameters']['sandboxID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned sandbox details. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['SandboxRecord'] - } - } - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/admin/clusters': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** - * Create a cluster - * @description Creates a cluster whose configuration cannot be modified. The cluster starts with deletion protection turned on. - */ - post: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['AdminClusterCreateRequest'] - } - } - responses: { - /** @description Cluster created. */ - 201: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['AdminClusterCreateResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/admin/clusters/{clusterID}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - post?: never - /** - * Delete a cluster and its environments - * @description Deletes a cluster after all team assignments are detached. In the same transaction, soft-deletes every live template, snapshot template and paused sandbox snapshot on the cluster, releases their aliases, and releases all environment references while preserving environment and build history. A team assignment rejects the deletion and leaves the environments untouched, and so does deletion protection on the cluster. The local cluster's nil ID is rejected. Repeating a completed deletion succeeds. - */ - delete: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Cluster deleted or already absent. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/clusters/{clusterID}/projects': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * List the projects assigned to a cluster (v1). - * @description Returns the ID of every project whose team is assigned to the cluster, whichever way it was assigned. A deleted project is never assigned. An absent cluster has no projects. - */ - get: operations['managementListClusterProjects'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/clusters/{clusterID}/deletion': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - /** - * Record that a cluster is being deleted (v1). - * @description Records that the caller is deleting the cluster, for a caller that deletes the cluster's teams before the cluster itself. Refused while the cluster has deletion protection turned on; a protection change in flight commits before or after the check, never between. Once recorded, deletion protection cannot be turned on, so the cluster delete that follows is not refused for protection, and no team can be newly assigned to the cluster. An assignment either commits before the deletion is recorded, where the caller's next project listing finds it, or is refused with cluster_assignment_cluster_deleting. Succeeds when the cluster is absent. Repeating the request succeeds. The local cluster's nil ID is rejected. - */ - put: operations['managementRequestClusterDeletion'] - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/clusters/{clusterID}/destroy-readiness': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * Check cluster destroy readiness - * @description Checks whether the cluster has deletion protection turned on, or more than one team uses this exact cluster, either as an assigned team or as the owner of active templates or snapshots on it. One team's own templates and snapshots do not block this check, because deleting the cluster soft-deletes them. Soft-deleted history does not count. Returns success if the cluster is absent. This read does not change resources or prevent later assignments or template creation. - */ - get: operations['managementClusterDestroyReadiness'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/admin/teams/{teamID}/cluster': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * Get a team's assigned cluster - * @description Returns the current cluster assignment without exposing cluster credentials. - */ - get: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the team. */ - teamID: components['parameters']['teamID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Cluster assignment returned. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['AdminTeamClusterAssignmentResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/admin/teams/{teamID}/ban': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - /** - * Ban a team - * @description Marks the team as banned so its API keys stop authenticating. Idempotent; running workloads are not touched. - */ - put: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the team. */ - teamID: components['parameters']['teamID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Team banned. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - post?: never - /** - * Unban a team - * @description Clears the team's ban. Idempotent. - */ - delete: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the team. */ - teamID: components['parameters']['teamID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Team unbanned. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - options?: never - head?: never - patch?: never - trace?: never - } - '/admin/user-profiles/resolve': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** Resolve user profiles */ - post: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['AdminAuthProviderProfilesResolveRequest'] - } - } - responses: { - /** @description Successfully resolved profiles. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['AdminAuthProviderProfilesResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 500: components['responses']['500'] - } - } - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * List team templates - * @description Returns a paginated list of the team's templates (and default templates inline, unless the team is on a dedicated cluster), with filtering, search, and column sorting via keyset cursor pagination. - */ - get: { - parameters: { - query?: { - /** @description Filter templates by visibility (true = public, false = internal). */ - public?: components['parameters']['templates_public'] - /** @description Case-insensitive substring match on template names, aliases, and template id. */ - search?: components['parameters']['templates_search'] - /** @description Sort column and direction. */ - sort?: components['parameters']['templates_sort'] - /** @description Maximum number of items to return per page. */ - limit?: components['parameters']['templates_limit'] - /** @description Cursor returned by the previous list response in `{sort}|{value}|{templateID}` format. Rejected if its sort does not match the request. */ - cursor?: components['parameters']['templates_cursor'] - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned paginated templates. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['TeamTemplatesResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 403: components['responses']['403'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates/defaults': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * List default templates - * @description Returns the list of default templates with their latest build info and aliases. - */ - get: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned default templates. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['DefaultTemplatesResponse'] - } - } - 401: components['responses']['401'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates/{templateID}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * Template - * @description Returns a single template owned by the current team. Dashboard-shaped read, indexed by template ID. - */ - get: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the template. */ - templateID: components['parameters']['templateID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned the template. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['TemplateDetail'] - } - } - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates/{templateID}/tags/groups': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * List template tag groups - * @description Returns ready template tag assignment groups with bounded per-tag history, paginated over tags with keyset cursor. - */ - get: { - parameters: { - query?: { - /** @description Maximum number of ready assignment rows to return per tag. */ - assignmentLimit?: components['parameters']['tag_assignment_limit'] - /** @description Maximum number of distinct tags to return per page. */ - tagsLimit?: components['parameters']['tag_groups_limit'] - /** @description Cursor returned by the previous list response in `{sort}|{latest_assigned_at}|{tag}` format (sort-tagged, RFC3339Nano). */ - tagsCursor?: components['parameters']['tag_groups_cursor'] - /** @description Case-insensitive substring filter on tag name. Allowed characters are `a-z`, `0-9`, `.`, `_`, `-`. */ - search?: components['parameters']['tag_groups_search'] - /** @description Sort order for the returned tag groups. */ - sort?: components['parameters']['tag_groups_sort'] - } - header?: never - path: { - /** @description Identifier of the template. */ - templateID: components['parameters']['templateID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned template tag groups. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['TemplateTagGroupsResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates/{templateID}/tags/count': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * Count template tags - * @description Returns the total number of distinct ready tags for the template. - */ - get: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the template. */ - templateID: components['parameters']['templateID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned tag count. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['TemplateTagsCountResponse'] - } - } - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates/{templateID}/tags/exists': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * Check ready template tag existence - * @description Checks whether a template tag has at least one ready assignment. - */ - get: { - parameters: { - query: { - /** @description Template tag name to check. */ - tag: components['parameters']['tag'] - } - header?: never - path: { - /** @description Identifier of the template. */ - templateID: components['parameters']['templateID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully checked template tag existence. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['TemplateTagExistsResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/templates/{templateID}/tags/{tag}/assignments': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * List ready assignments for a single template tag - * @description Returns ready tag assignment events for a single tag, ordered newest first, with keyset cursor pagination. - */ - get: { - parameters: { - query?: { - /** @description Cursor returned by the previous list response in `assigned_at|assignment_id` format. */ - cursor?: components['parameters']['tag_assignments_cursor'] - /** @description Maximum number of assignment rows to return per page. */ - limit?: components['parameters']['tag_assignments_limit'] - } - header?: never - path: { - /** @description Identifier of the template. */ - templateID: components['parameters']['templateID'] - /** @description Template tag name. */ - tag: components['parameters']['tag_path'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned tag assignment page. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['TemplateTagAssignmentsResponse'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 403: components['responses']['403'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/projects/{projectID}': { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - get?: never - /** Create or reconcile a project (v1). */ - put: operations['managementUpsertProject'] - post?: never - /** - * Soft-delete a project (v1). - * @description Marks the project deleted and blocked, releases its cluster assignment, scrubs its name and email to DELETED and its slug to deleted-, which releases the old slug for reuse, deletes its memberships and API keys, soft-deletes its templates and snapshots, and enqueues the teardown of its running sandboxes, all in one transaction. The project row remains. Afterwards the project answers 404 to other management writes. Repeating the call answers 204 and repeats every step except the deletion time: it re-blocks and re-scrubs the project, clears a cluster assignment written since, deletes any memberships, keys, templates or snapshots created since, and enqueues the teardown again. - */ - delete: operations['managementDeleteProject'] - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/projects/{projectID}/members/{userID}': { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - /** @description Identifier of the user. */ - userID: components['parameters']['userID'] - } - cookie?: never - } - get?: never - /** - * Apply one versioned project member projection (v1). - * @description Applies the newest desired presence for one project member. An older or duplicate revision is accepted without changing target state. - */ - put: operations['managementApplyProjectMember'] - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/projects/{projectID}/limits': { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - get?: never - /** Reconcile effective limits for a project (v1). */ - put: operations['managementUpsertProjectLimits'] - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/projects/{projectID}/block': { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - get?: never - /** - * Apply a project's block state (v1). - * @description Records whether the project is refused billable work, which the auth path reads before it lets a sandbox start. An older or duplicate revision is accepted without changing target state. - */ - put: operations['managementApplyProjectBlock'] - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/clusters/{clusterID}': { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - get?: never - /** - * Register a cluster (v1). - * @description A cluster registered here for the first time starts with deletion protection turned off. Registering an existing cluster again with the same name and auth organization updates its endpoint, TLS setting, sandbox proxy domain and token and keeps its stored deletion protection; a different name or auth organization returns a conflict. - */ - put: operations['managementRegisterCluster'] - post?: never - /** - * Delete a cluster and its environments (v1). - * @description Deletes a cluster after all team assignments are detached. In the same transaction, soft-deletes every live template, snapshot template and paused sandbox snapshot on the cluster, releases their aliases, and releases all environment references while preserving environment and build history. A team assignment rejects the deletion and leaves the environments untouched, and so does deletion protection on the cluster. The local cluster's nil ID is rejected. Repeating a completed deletion succeeds. - */ - delete: operations['managementDeleteCluster'] - options?: never - head?: never - patch?: never - trace?: never - } - '/v1/management/projects/{projectID}/cluster/{clusterID}': { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - get?: never - /** - * Assign a cluster to a project (v1). - * @description Assigns the cluster when the project has no cluster assigned. Replaying the identical assignment succeeds; replacing a different assigned cluster returns a conflict. - */ - put: operations['managementAssignProjectCluster'] - post?: never - /** Detach a cluster assignment from a project (v1). */ - delete: operations['managementDetachProjectCluster'] - options?: never - head?: never - patch?: never - trace?: never - } -} -export type webhooks = Record -export interface components { - schemas: { - Error: { - /** - * Format: int32 - * @description Error code. - */ - code: number - error_code?: components['schemas']['ErrorCode'] - /** @description Error message. */ - message: string - } - /** - * @description Stable machine-readable reason for an API error. New codes may be added as other errors adopt this contract. Clients must handle absent or unknown codes without interpreting the human-readable message. - * @enum {string} - */ - ErrorCode: - | 'cluster_registration_invalid' - | 'cluster_registration_conflict' - | 'cluster_assignment_invalid' - | 'cluster_assignment_project_not_found' - | 'cluster_assignment_cluster_not_found' - | 'cluster_assignment_already_assigned' - | 'cluster_assignment_cluster_deleting' - AdminAuthProviderProfile: { - /** - * Format: uuid - * @description Internal E2B user identifier. - */ - userId: string - /** @description Email address from the configured auth provider. */ - email: string | null - } - AdminAuthProviderProfilesResponse: { - profiles: components['schemas']['AdminAuthProviderProfile'][] - } - AdminAuthProviderProfilesResolveRequest: { - userIds: string[] - } - AdminClusterCreateRequest: { - /** - * Format: uuid - * @description Optional stable identifier for idempotent creation. Reuse succeeds only when the immutable configuration is identical. - */ - cluster_id?: string - name: string - endpoint: string - endpoint_tls: boolean - token: string - sandbox_proxy_domain?: string | null - auth_org_id?: string | null - } - AdminClusterCreateResponse: { - /** Format: uuid */ - cluster_id: string - } - AdminTeamClusterAssignmentResponse: { - /** Format: uuid */ - cluster_id: string - } - /** - * @description Build status mapped for dashboard clients. - * @enum {string} - */ - BuildStatus: 'building' | 'failed' | 'success' - ListedBuild: { - /** - * Format: uuid - * @description Identifier of the build. - */ - id: string - /** @description Template alias when present, otherwise template ID. */ - template: string - /** @description Identifier of the template. */ - templateId: string - status: components['schemas']['BuildStatus'] - /** @description Failure message when status is `failed`, otherwise `null`. */ - statusMessage: string | null - /** - * Format: date-time - * @description Build creation timestamp in RFC3339 format. - */ - createdAt: string - /** - * Format: date-time - * @description Build completion timestamp in RFC3339 format, if finished. - */ - finishedAt: string | null - /** - * Format: int64 - * @description CPU cores configured for the build. - */ - cpuCount: number - /** - * Format: int64 - * @description Memory in MiB configured for the build. - */ - memoryMB: number - /** - * Format: int64 - * @description Disk size in MiB configured for the build, if recorded. - */ - diskSizeMB: number | null - /** @description envd version the build was created with, if recorded. */ - envdVersion: string | null - } - BuildsListResponse: { - data: components['schemas']['ListedBuild'][] - /** @description Cursor to pass to the next list request, or `null` if there is no next page. */ - nextCursor: string | null - } - BuildStatusItem: { - /** - * Format: uuid - * @description Identifier of the build. - */ - id: string - status: components['schemas']['BuildStatus'] - /** - * Format: date-time - * @description Build completion timestamp in RFC3339 format, if finished. - */ - finishedAt: string | null - /** @description Failure message when status is `failed`, otherwise `null`. */ - statusMessage: string | null - } - BuildsStatusesResponse: { - /** @description List of build statuses */ - buildStatuses: components['schemas']['BuildStatusItem'][] - } - BuildInfo: { - /** @description Template names related to this build, if available. */ - names?: string[] | null - /** - * Format: date-time - * @description Build creation timestamp in RFC3339 format. - */ - createdAt: string - /** - * Format: date-time - * @description Build completion timestamp in RFC3339 format, if finished. - */ - finishedAt: string | null - status: components['schemas']['BuildStatus'] - /** @description Failure message when status is `failed`, otherwise `null`. */ - statusMessage: string | null - } - /** - * Format: int64 - * @description CPU cores for the sandbox - */ - CPUCount: number - /** - * Format: int64 - * @description Memory for the sandbox in MiB - */ - MemoryMB: number - /** - * Format: int64 - * @description Disk size for the sandbox in MiB - */ - DiskSizeMB: number - SandboxRecord: { - /** @description Identifier of the template from which is the sandbox created */ - templateID: string - /** @description Alias of the template */ - alias?: string - /** @description Identifier of the sandbox */ - sandboxID: string - /** - * Format: date-time - * @description Time when the sandbox was started - */ - startedAt: string - /** - * Format: date-time - * @description Time when the sandbox was stopped - */ - stoppedAt?: string | null - /** @description Base domain where the sandbox traffic is accessible */ - domain?: string | null - cpuCount: components['schemas']['CPUCount'] - memoryMB: components['schemas']['MemoryMB'] - diskSizeMB: components['schemas']['DiskSizeMB'] - /** @description Whether the sandbox ended more than the fixed retention window ago, so its monitoring and logs data is no longer available */ - retentionExpired: boolean - /** @description Whether the sandbox ended more than the team's events retention window ago, so its events data is no longer available */ - eventsRetentionExpired: boolean - } - HealthResponse: { - /** @description Human-readable health check result. */ - message: string - } - DefaultTemplateAlias: { - alias: string - namespace?: string | null - } - DefaultTemplate: { - id: string - aliases: components['schemas']['DefaultTemplateAlias'][] - /** Format: uuid */ - buildId: string - /** Format: int64 */ - ramMb: number - /** Format: int64 */ - vcpu: number - /** Format: int64 */ - totalDiskSizeMb: number | null - envdVersion?: string | null - /** Format: date-time */ - createdAt: string - public: boolean - /** Format: int32 */ - buildCount: number - /** Format: int64 */ - spawnCount: number - } - DefaultTemplatesResponse: { - templates: components['schemas']['DefaultTemplate'][] - } - TeamTemplate: { - templateID: string - /** Format: uuid */ - buildID: string - /** Format: int64 */ - cpuCount: number - /** Format: int64 */ - memoryMB: number - /** Format: int64 */ - diskSizeMB: number | null - public: boolean - aliases: string[] - names: string[] - /** Format: date-time */ - createdAt: string - /** Format: date-time */ - updatedAt: string - createdBy: { - /** Format: uuid */ - id: string - email?: string | null - } | null - /** Format: date-time */ - lastSpawnedAt: string | null - /** Format: int64 */ - spawnCount: number - /** Format: int32 */ - buildCount: number - envdVersion: string | null - isDefault: boolean - defaultDescription?: string | null - } - TeamTemplatesResponse: { - data: components['schemas']['TeamTemplate'][] - /** @description Cursor to pass to the next list request, or `null` if there is no next page. */ - nextCursor: string | null - } - /** @description Dashboard-shaped single-template read. Mirrors the infra-api `Template` schema fields the dashboard renders. */ - TemplateDetail: { - /** @description Identifier of the template. */ - templateID: string - /** @description Identifier of the latest ready build for the template, or the zero UUID when none. */ - buildID: string - /** @description vCPU count of the latest ready default build, or `null` when none. */ - cpuCount: components['schemas']['CPUCount'] | null - /** @description Memory in MiB of the latest ready default build, or `null` when none. */ - memoryMB: components['schemas']['MemoryMB'] | null - /** @description Disk size in MiB of the latest ready default build, or `null` when none. */ - diskSizeMB: components['schemas']['DiskSizeMB'] | null - /** @description Whether the template is public or only accessible by the team. */ - public: boolean - /** - * @deprecated - * @description Aliases of the template. - */ - aliases: string[] - /** @description Names of the template (namespace/alias format when namespaced). */ - names: string[] - /** - * Format: date-time - * @description Time when the template was created. - */ - createdAt: string - /** - * Format: date-time - * @description Time when the template was last updated. - */ - updatedAt: string - /** - * Format: date-time - * @description Time when the template was last used. - */ - lastSpawnedAt: string | null - /** - * Format: int64 - * @description Number of times the template was used. - */ - spawnCount: number - /** - * Format: int32 - * @description Number of times the template was built. - */ - buildCount: number - /** @description envd version of the latest ready default build, or `null` when none. */ - envdVersion: string | null - } - TemplateTagAssignment: { - /** - * Format: uuid - * @description Identifier of the tag assignment event. - */ - assignmentId: string - /** - * Format: uuid - * @description Identifier of the assigned build. - */ - buildId: string - /** - * Format: date-time - * @description Time when the tag was assigned to the build. - */ - assignedAt: string - /** - * Format: date-time - * @description Time when the assigned build was created. - */ - buildCreatedAt: string - /** - * Format: date-time - * @description Time when the assigned build finished. - */ - buildFinishedAt: string | null - } - TemplateTagGroup: { - /** @description Template tag name. */ - tag: string - /** @description Ready assignment events for this tag, sorted latest first. */ - assignments: components['schemas']['TemplateTagAssignment'][] - /** @description Whether more ready assignment events exist beyond the requested assignment limit. */ - hasMore: boolean - } - TemplateTagGroupsResponse: { - tags: components['schemas']['TemplateTagGroup'][] - /** @description Cursor to pass as `tagsCursor` for the next page, or `null` if there is no next page. */ - nextCursor: string | null - } - TemplateTagsCountResponse: { - /** - * Format: int64 - * @description Total distinct ready tags for the template. - */ - total: number - } - TemplateTagExistsResponse: { - /** @description Whether the template tag has at least one ready assignment. */ - exists: boolean - /** @description Normalized template tag name. */ - normalizedTag: string - } - TemplateTagAssignmentsResponse: { - /** @description Ready assignment events for the tag, sorted latest first. */ - data: components['schemas']['TemplateTagAssignment'][] - /** @description Cursor to pass to the next list request, or `null` if there is no next page. */ - nextCursor: string | null - } - /** - * @description The properties of a project this side stores. Name, slug and email are synchronized on every push. The optional cluster assignment is preserved when omitted. - * - * A project's tier is not among them. It is assigned once, at creation, from this side's own default, and no push moves it — limits arrive separately and in full through upsertProjectLimits, and none is resolved from the tier. - */ - ManagementProjectUpsertRequest: { - /** - * Format: uuid - * @description Cluster assignment to synchronize. Omission preserves the existing assignment. - */ - cluster_id?: string - name: string - /** @description Changing it renames the project, and its template names follow: each name held under an earlier slug is also given under the new one, and the old names keep working. A name the new slug already holds is left as it is. A slug already held on this control plane is a 409, on a rename as much as on a create. */ - slug: string - /** @description Contact address recorded on the project. */ - email: string - } - ManagementProject: components['schemas']['ManagementProjectUpsertRequest'] & { - /** Format: uuid */ - id: string - } - ManagementClusterProjects: { - project_ids: string[] - } - ManagementClusterRegistrationRequest: { - name: string - endpoint: string - endpoint_tls: boolean - token: string - sandbox_proxy_domain?: string | null - auth_org_id?: string | null - } - ManagementProjectMemberIdentity: { - issuer: string - subject: string - } - ManagementProjectMemberApplyRequest: { - /** Format: int64 */ - revision: number - present: boolean - /** - * @description Whether this membership is the user's default team. Omitted requests from older sources remain non-default. - * @default false - */ - is_default: boolean - identities?: components['schemas']['ManagementProjectMemberIdentity'][] - } - /** @description Desired project block state. A newer revision replaces the state, including any intervening manual block or unblock. */ - ManagementProjectBlockRequest: { - /** - * Format: int64 - * @description Monotonic per-project revision. Older or duplicate revisions succeed without changing stored state. - */ - revision: number - /** @description Whether the project is refused billable work. */ - blocked: boolean - /** @description Reason included in blocked-request errors. Cleared when blocked is false. */ - reason?: string - decided_at?: components['schemas']['ManagementDecidedAt'] - } - /** - * Format: date-time - * @description When the caller decided this revision, in RFC 3339. Stored with the revision and used to measure how long the decision took to apply here. Omit it when the caller does not know; the delivery is then counted as origin unknown rather than as zero lag. - */ - ManagementDecidedAt: string - /** - * @description A project's effective limits, already resolved by the caller. Every field is absolute: this side stores what it is given and performs no arithmetic of its own. - * - * The minimums below are the contract for what a limit may be, at least as strict as the CHECK constraints on default_limits. project_limits stores the same values under looser constraints on purpose — it is a push target, and a floor that only rejects the impossible keeps a future decision about what is allowed a change to this schema rather than a migration. - * - * `max_disk_size_mb` and `max_free_disk_size_mb` are one ceiling under two names, and either name alone carries it. A caller that sends both must send them equal; a caller that sends neither is refused. Sending both is what a caller does while receivers older than the second name are still running. - */ - ManagementProjectLimits: - | { - /** - * Format: int64 - * @description The caller's version of this answer, raised whenever the limits it resolved for the project change. Delivery is over a network, so two pushes can be in flight at once and arrive in either order: this side stores the revision it accepted and drops a delivery at or below it, which is what keeps a delayed retry from putting the project back on limits it has already left. - * - * Comparable only against earlier revisions for the same project. - */ - revision: number - /** Format: int32 */ - concurrent_sandboxes: number - /** Format: int32 */ - max_sandbox_length_hours: number - /** Format: int32 */ - max_vcpu: number - /** Format: int64 */ - max_ram_mb: number - /** Format: int64 */ - disk_mb: number - /** Format: int32 */ - concurrent_template_builds: number - /** Format: int32 */ - events_ttl_days: number - /** - * Format: int64 - * @description The default free-space growth target when a template build request omits one. May sit anywhere at or below the maximum free-space growth target, and usually sits well below it; a delivery whose default exceeds the maximum is rejected. - */ - default_free_disk_size_mb: number - /** - * Format: int64 - * @description The most a template build may request as its free-space growth target. - */ - max_free_disk_size_mb?: number - /** - * Format: int64 - * @description The same ceiling as max_free_disk_size_mb, under the name it was first published with. Kept until every deployed sender and receiver speaks the other name. - */ - max_disk_size_mb?: number - /** - * Format: int64 - * @description Requests per second a project may make to each list endpoint. Zero disables the limit. - */ - api_team_rps_list: number - /** - * Format: int64 - * @description Requests per second a project may make to each delete endpoint. Zero disables the limit. - */ - api_team_rps_delete: number - /** - * Format: int32 - * @description Live secrets the project may hold. - */ - max_secrets: number - decided_at?: components['schemas']['ManagementDecidedAt'] - } - | unknown - | unknown - } - responses: { - /** @description Bad request */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Authentication error */ - 401: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Not found */ - 404: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Conflict */ - 409: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Server error */ - 500: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Operation is not implemented */ - 501: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - } - parameters: { - /** @description Identifier of the build. */ - build_id: string - /** @description Identifier of the sandbox. */ - sandboxID: string - /** @description Maximum number of items to return per page. */ - builds_limit: number - /** @description Cursor returned by the previous list response in `created_at|build_id` format. */ - builds_cursor: string - /** @description Optional filter by build identifier, template identifier, or template alias. */ - build_id_or_template: string - /** @description Comma-separated list of build statuses to include. */ - build_statuses: components['schemas']['BuildStatus'][] - /** @description Comma-separated list of build IDs to get statuses for. */ - build_ids: string[] - /** @description Identifier of the team. */ - teamID: string - /** @description Identifier of the cluster. */ - clusterID: string - /** @description Identifier of the project. */ - projectID: string - /** @description Identifier of the user. */ - userID: string - /** @description Maximum number of items to return per page. */ - templates_limit: number - /** @description Identifier of the template. */ - templateID: string - /** @description Template tag name to check. */ - tag: string - /** @description Maximum number of ready assignment rows to return per tag. */ - tag_assignment_limit: number - /** @description Template tag name. */ - tag_path: string - /** @description Maximum number of assignment rows to return per page. */ - tag_assignments_limit: number - /** @description Cursor returned by the previous list response in `{sort}|{value}|{templateID}` format. Rejected if its sort does not match the request. */ - templates_cursor: string - /** @description Filter templates by visibility (true = public, false = internal). */ - templates_public: boolean - /** @description Case-insensitive substring match on template names, aliases, and template id. */ - templates_search: string - /** @description Sort column and direction. */ - templates_sort: - | 'created_at_asc' - | 'created_at_desc' - | 'updated_at_asc' - | 'updated_at_desc' - /** @description Cursor returned by the previous list response in `assigned_at|assignment_id` format. */ - tag_assignments_cursor: string - /** @description Maximum number of distinct tags to return per page. */ - tag_groups_limit: number - /** @description Cursor returned by the previous list response in `{sort}|{latest_assigned_at}|{tag}` format (sort-tagged, RFC3339Nano). */ - tag_groups_cursor: string - /** @description Case-insensitive substring filter on tag name. Allowed characters are `a-z`, `0-9`, `.`, `_`, `-`. */ - tag_groups_search: string - /** @description Sort order for the returned tag groups. */ - tag_groups_sort: 'latest_desc' | 'latest_asc' | 'name_asc' | 'name_desc' - } - requestBodies: never - headers: never - pathItems: never -} -export type $defs = Record -export interface operations { - managementListClusterProjects: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description The projects assigned to the cluster. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ManagementClusterProjects'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 500: components['responses']['500'] - } - } - managementRequestClusterDeletion: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description The deletion is recorded, or the cluster is absent. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - /** @description The cluster has deletion protection turned on. */ - 409: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - 500: components['responses']['500'] - } - } - managementClusterDestroyReadiness: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description At most one team uses the cluster. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - /** @description The cluster has deletion protection turned on, or more than one team uses it and the other teams and their templates must move off it before destroying the cluster. */ - 409: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - 500: components['responses']['500'] - } - } - managementUpsertProject: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['ManagementProjectUpsertRequest'] - } - } - responses: { - /** @description Existing project reconciled. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ManagementProject'] - } - } - /** @description Project created. */ - 201: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ManagementProject'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 409: components['responses']['409'] - 500: components['responses']['500'] - 501: components['responses']['501'] - } - } - managementDeleteProject: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Project is deleted and its teardown is enqueued. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - managementApplyProjectMember: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - /** @description Identifier of the user. */ - userID: components['parameters']['userID'] - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['ManagementProjectMemberApplyRequest'] - } - } - responses: { - /** @description Membership projection is applied or already superseded. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } - managementUpsertProjectLimits: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['ManagementProjectLimits'] - } - } - responses: { - /** @description Effective limits are synchronized. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - 501: components['responses']['501'] - } - } - managementApplyProjectBlock: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['ManagementProjectBlockRequest'] - } - } - responses: { - /** @description Block state is applied or already superseded. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - 501: components['responses']['501'] - } - } - managementRegisterCluster: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['ManagementClusterRegistrationRequest'] - } - } - responses: { - /** @description Cluster registration is present. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } - managementDeleteCluster: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Cluster is absent. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } - managementAssignProjectCluster: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Cluster is assigned to the project. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } - managementDetachProjectCluster: { - parameters: { - query?: never - header?: never - path: { - /** @description Identifier of the project. */ - projectID: components['parameters']['projectID'] - /** @description Identifier of the cluster. */ - clusterID: components['parameters']['clusterID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description The matching assignment is absent. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 409: components['responses']['409'] - 500: components['responses']['500'] - } - } -} diff --git a/src/lib/env.ts b/src/lib/env.ts index e01e3eb20..590ec0228 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -24,13 +24,11 @@ export const runtimeEnvSchema = z.object({ PUBLIC_E2B_DOMAIN: z.string().trim().min(1, 'Set PUBLIC_E2B_DOMAIN'), PUBLIC_SANDBOX_URL: httpUrl, E2B_INFRA_API_URL: httpUrl, - E2B_DASHBOARD_API_URL: httpUrl, // Reject the SDK alias too, or its own environment fallback can override routing. E2B_SANDBOX_URL: deprecatedVariable('PUBLIC_SANDBOX_URL'), NEXT_PUBLIC_E2B_DOMAIN: deprecatedVariable('PUBLIC_E2B_DOMAIN'), NEXT_PUBLIC_INFRA_API_URL: deprecatedVariable('E2B_INFRA_API_URL'), - NEXT_PUBLIC_DASHBOARD_API_URL: deprecatedVariable('E2B_DASHBOARD_API_URL'), NEXT_PUBLIC_E2B_SANDBOX_URL: deprecatedVariable('PUBLIC_SANDBOX_URL'), }) diff --git a/tests/integration/health-route.test.ts b/tests/integration/health-route.test.ts index 0e4206307..00ae38c9b 100644 --- a/tests/integration/health-route.test.ts +++ b/tests/integration/health-route.test.ts @@ -1,12 +1,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { apiGet } = vi.hoisted(() => ({ - apiGet: vi.fn(), +const { infraGet } = vi.hoisted(() => ({ + infraGet: vi.fn(), })) vi.mock('@/core/shared/clients/api', () => ({ - api: { - GET: apiGet, + infra: { + GET: infraGet, }, })) @@ -24,8 +24,8 @@ describe('/api/health', () => { vi.clearAllMocks() }) - it('returns ok when dashboard-api is healthy', async () => { - apiGet.mockResolvedValue({ error: null }) + it('returns ok when infra-api is healthy', async () => { + infraGet.mockResolvedValue({ error: null }) const response = await GET() const body = await response.json() @@ -33,18 +33,18 @@ describe('/api/health', () => { expect(response.status).toBe(200) expect(body).toEqual({ status: 'ok', - checks: { dashboardApi: true }, + checks: { infraApi: true }, }) }) - it('returns degraded when required dashboard-api is unhealthy', async () => { - apiGet.mockResolvedValue({ error: { message: 'downstream unavailable' } }) + it('returns degraded when infra-api is unhealthy', async () => { + infraGet.mockResolvedValue({ error: { message: 'downstream unavailable' } }) const response = await GET() const body = await response.json() expect(response.status).toBe(503) expect(body.status).toBe('degraded') - expect(body.checks).toEqual({ dashboardApi: false }) + expect(body.checks).toEqual({ infraApi: false }) }) }) diff --git a/tests/integration/runtime-config-layout.test.tsx b/tests/integration/runtime-config-layout.test.tsx index b57c8d8dd..209c71654 100644 --- a/tests/integration/runtime-config-layout.test.tsx +++ b/tests/integration/runtime-config-layout.test.tsx @@ -46,7 +46,6 @@ describe('dashboard layout runtime config', () => { vi.stubGlobal('fetch', fetchSpy) vi.stubEnv('E2B_API_KEY', 'e2b_test_private_key') vi.stubEnv('E2B_INFRA_API_URL', 'http://infra-api.internal:3000') - vi.stubEnv('E2B_DASHBOARD_API_URL', 'http://dashboard-api.internal:3010') for (const domain of ['first.example', 'second.example']) { vi.stubEnv('PUBLIC_E2B_DOMAIN', domain) @@ -61,9 +60,7 @@ describe('dashboard layout runtime config', () => { expect(html).toContain( `${domain}|https://sandbox.${domain}` ) - expect(html).not.toMatch( - /e2b_test_private_key|infra-api\.internal|dashboard-api\.internal/ - ) + expect(html).not.toMatch(/e2b_test_private_key|infra-api\.internal/) } expect(fetchSpy).not.toHaveBeenCalled() }) diff --git a/tests/integration/runtime-config-startup.test.ts b/tests/integration/runtime-config-startup.test.ts index 06ddd5200..8be3a41c7 100644 --- a/tests/integration/runtime-config-startup.test.ts +++ b/tests/integration/runtime-config-startup.test.ts @@ -3,16 +3,11 @@ import { register } from '@/instrumentation' vi.mock('@vercel/otel', () => ({ registerOTel: vi.fn() })) -const URL_KEYS = [ - 'PUBLIC_SANDBOX_URL', - 'E2B_INFRA_API_URL', - 'E2B_DASHBOARD_API_URL', -] as const +const URL_KEYS = ['PUBLIC_SANDBOX_URL', 'E2B_INFRA_API_URL'] as const const DEPRECATED_KEYS = [ 'NEXT_PUBLIC_E2B_DOMAIN', 'NEXT_PUBLIC_INFRA_API_URL', - 'NEXT_PUBLIC_DASHBOARD_API_URL', 'NEXT_PUBLIC_E2B_SANDBOX_URL', 'E2B_SANDBOX_URL', ] as const @@ -76,7 +71,6 @@ describe('runtime configuration at server startup', () => { it('accepts a valid runtime configuration with telemetry disabled', async () => { vi.stubEnv('PUBLIC_SANDBOX_URL', 'https://sandbox.example') vi.stubEnv('E2B_INFRA_API_URL', 'http://127.0.0.1:3000') - vi.stubEnv('E2B_DASHBOARD_API_URL', 'http://127.0.0.1:3010') vi.stubEnv('DASHBOARD_COOKIE_SECURE', ' false ') await expect(register()).resolves.toBeUndefined() diff --git a/tests/unit/env.test.ts b/tests/unit/env.test.ts index def474faa..974febc4a 100644 --- a/tests/unit/env.test.ts +++ b/tests/unit/env.test.ts @@ -5,7 +5,6 @@ const domain = { PUBLIC_E2B_DOMAIN: 'cluster.example' } const deprecatedVariables = [ ['NEXT_PUBLIC_E2B_DOMAIN', 'PUBLIC_E2B_DOMAIN'], ['NEXT_PUBLIC_INFRA_API_URL', 'E2B_INFRA_API_URL'], - ['NEXT_PUBLIC_DASHBOARD_API_URL', 'E2B_DASHBOARD_API_URL'], ['NEXT_PUBLIC_E2B_SANDBOX_URL', 'PUBLIC_SANDBOX_URL'], ['E2B_SANDBOX_URL', 'PUBLIC_SANDBOX_URL'], ] as const @@ -38,7 +37,6 @@ describe('dashboard environment validation', () => { PUBLIC_E2B_DOMAIN: ' cluster.example ', PUBLIC_SANDBOX_URL: ' https://sandbox.cluster.example ', E2B_INFRA_API_URL: ' http://127.0.0.1:3000 ', - E2B_DASHBOARD_API_URL: ' ', DASHBOARD_COOKIE_SECURE: ' FALSE ', OTEL_EXPORTER_OTLP_ENDPOINT: '', }) @@ -46,12 +44,18 @@ describe('dashboard environment validation', () => { ...domain, PUBLIC_SANDBOX_URL: 'https://sandbox.cluster.example', E2B_INFRA_API_URL: 'http://127.0.0.1:3000', - E2B_DASHBOARD_API_URL: undefined, DASHBOARD_COOKIE_SECURE: 'false', OTEL_EXPORTER_OTLP_ENDPOINT: undefined, }) }) + // Upgrades must not fail on settings for services the dashboard no longer uses. + it('ignores variables it does not read', () => { + expect( + appEnvSchema.parse({ ...domain, E2B_RETIRED_API_URL: 'not a url' }) + ).toEqual(domain) + }) + it.each( deprecatedVariables )('reports how to migrate %s', (key, replacement) => { @@ -75,7 +79,6 @@ describe('dashboard environment validation', () => { it.each([ 'PUBLIC_SANDBOX_URL', 'E2B_INFRA_API_URL', - 'E2B_DASHBOARD_API_URL', ])('requires an HTTP(S) URL for %s', (key) => { for (const value of ['localhost:3002', 'ftp://sandbox.example']) { const parsed = appEnvSchema.safeParse({ ...domain, [key]: value }) diff --git a/tests/unit/runtime-config.test.ts b/tests/unit/runtime-config.test.ts index 40d4810f2..4a83b8cff 100644 --- a/tests/unit/runtime-config.test.ts +++ b/tests/unit/runtime-config.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { resolveBrowserRuntimeConfig, - resolveDashboardApiUrl, resolveE2BDomain, resolveInfraApiUrl, resolveSandboxUrl, @@ -11,10 +10,8 @@ beforeEach(() => { for (const key of [ 'PUBLIC_SANDBOX_URL', 'E2B_INFRA_API_URL', - 'E2B_DASHBOARD_API_URL', 'E2B_SANDBOX_URL', 'NEXT_PUBLIC_INFRA_API_URL', - 'NEXT_PUBLIC_DASHBOARD_API_URL', 'NEXT_PUBLIC_E2B_SANDBOX_URL', 'NEXT_PUBLIC_E2B_DOMAIN', ]) @@ -27,12 +24,11 @@ afterEach(() => { }) describe('runtime configuration', () => { - it('reads the public domain at runtime for the browser and both API defaults', () => { + it('reads the public domain at runtime for the browser and the API default', () => { for (const domain of ['first.example', 'second.example']) { vi.stubEnv('PUBLIC_E2B_DOMAIN', ` ${domain} `) expect(resolveE2BDomain()).toBe(domain) expect(resolveInfraApiUrl()).toBe(`https://api.${domain}`) - expect(resolveDashboardApiUrl()).toBe(`https://dashboard-api.${domain}`) expect(resolveBrowserRuntimeConfig()).toEqual({ domain, sandboxUrl: null, @@ -48,11 +44,6 @@ describe('runtime configuration', () => { it.each([ ['E2B_INFRA_API_URL', resolveInfraApiUrl, 'https://api.example.dev'], - [ - 'E2B_DASHBOARD_API_URL', - resolveDashboardApiUrl, - 'https://dashboard-api.example.dev', - ], ['PUBLIC_SANDBOX_URL', resolveSandboxUrl, undefined], ] as const)('uses the runtime override for %s and its default when blank', (key, resolve, fallback) => { vi.stubEnv(key, ' http://127.0.0.1:3002 ') @@ -73,7 +64,6 @@ describe('runtime configuration', () => { vi.stubEnv('PUBLIC_E2B_DOMAIN', 'runtime.example') vi.stubEnv('PUBLIC_SANDBOX_URL', 'https://sandbox.runtime.example') vi.stubEnv('E2B_INFRA_API_URL', 'http://infra-api.internal:3000') - vi.stubEnv('E2B_DASHBOARD_API_URL', 'http://dashboard-api.internal:3010') vi.stubEnv('E2B_API_KEY', 'e2b_test_private_key') expect(resolveBrowserRuntimeConfig()).toEqual({ domain: 'runtime.example', diff --git a/tsconfig.json b/tsconfig.json index fcc7781f0..7732a4d31 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,9 +20,6 @@ } ], "paths": { - "@/contracts/dashboard-api": [ - "./src/core/shared/contracts/dashboard-api.types.ts" - ], "@/contracts/infra-api": [ "./src/core/shared/contracts/infra-api.types.ts" ],