> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sandywp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rename a sandbox, or set whether it is permanent

> Send `name` to rename the sandbox (2-80 characters). The rename changes the display name only: the slug, sandbox URL, and admin login are unchanged. A body containing `name` is treated as a rename and `permanent` is ignored.

Send `permanent` to set whether a sandbox is permanent (never expires). Setting `true` requires a paid plan. Setting `false` on a currently-permanent sandbox reverts it to the default temporary lifespan starting now. A no-op call (value already matches) succeeds without changing `expiresAt`.



## OpenAPI

````yaml /openapi.yaml patch /api/app/sites/{id}
openapi: 3.1.0
info:
  title: SandyWP API
  version: 0.1.0
  summary: Complete supported customer API for SandyWP automation.
  description: >
    This is the complete supported customer API for automating SandyWP
    sandboxes, reusable

    Templates and Blueprints, Imports, Workspaces, account credentials,
    demo-launch webhooks, and

    OAuth connection setup.

    It is the stable HTTP contract for developer integrations; browser,
    provider, and operations

    routes on the same host are deliberately not part of this document.


    **Exclusions ledger.** The specification excludes guest/launcher creation
    (`POST /api/sites`),

    account profile/avatar/password/support and GitHub UI handlers, billing
    checkout/portal/sync,

    public launch routes, plugin/theme search proxies, storage, activity
    telemetry, newsletter,

    health, metrics, impersonation, Slack/GitHub callbacks, Git/Polar webhooks,
    all `/api/admin/**`,

    and all `/api/internal/**` worker or SSH-control routes. Those endpoints are
    browser backends,

    provider callbacks, or operational control-plane interfaces rather than
    supported developer APIs.


    ## Conventions


    - Requests and responses are JSON. Send `Content-Type: application/json` on
    writes (the file
      upload and plugin-ZIP-upload endpoints instead use `multipart/form-data`, as noted on those
      operations).
    - Sandboxes and Templates are addressed by their opaque `id` in the API (for
    example
      `site_abc123`, `tpl_xyz789`).
    - Errors use a consistent envelope — see the `Error` schema. Some actions
    require a paid plan
      and fail with HTTP `402` and a specific error code (for example
      `template_sharing_requires_paid_plan`); each is called out on the operation that returns it.
    - **Async by default.** Provisioning and other slow sandbox mutations happen
    on a worker, not
      inline in the HTTP request. An endpoint that starts one either returns a `jobId` to poll (see
      the `Job` / `JobSummary` schemas and each operation's description), returns the resource in a
      non-terminal state (a site `"creating"`, a Template `"building"`) to re-fetch until it settles,
      or both.
    - A personal bearer token can call the authenticated operations below. A
    scoped OAuth token is
      accepted only by `GET /api/account/me` and the Imports family; it needs `import:read` or
      `import:write` for the matching import operation. Other REST operations treat scoped tokens
      as unauthenticated.
  contact:
    name: SandyWP
    url: https://app.sandywp.com/docs/api
servers:
  - url: https://app.sandywp.com
security:
  - bearerAuth: []
tags:
  - name: Templates
    description: >-
      A Template is a snapshot of a finished sandbox — WordPress/PHP versions,
      plugins, themes, content, and database — ready to restore in seconds.
      Templates come from saving a ready sandbox, and can optionally be
      published as a public "launch this demo" link.
  - name: Sites
    description: >-
      A Site is a disposable WordPress sandbox: its lifecycle
      (create/restore/reset/delete) and the per-sandbox tools built on top of it
      (PHP runtime & ini config, WordPress debug settings, database access,
      outgoing-email capture, plugin deploys, the file manager, Git deployment,
      and SSH access).
  - name: Blueprints
    description: >-
      Reviewable WordPress build recipes that can be validated, saved, run on a
      sandbox, or baked into a Template.
  - name: Imports
    description: >-
      Archive-upload workflows for cloning a live WordPress site or pushing a
      local installation. Scoped OAuth tokens may use only this family, with
      `import:read` for reads and `import:write` for mutations.
  - name: Workspaces
    description: Workspace selection plus member and custom-role administration.
  - name: Account
    description: >-
      Identity, usage, personal API tokens, demo-launch webhooks, and account
      SSH public keys.
  - name: OAuth
    description: >-
      Public-client registration and PKCE authorization-code token exchange for
      supported connectors.
paths:
  /api/app/sites/{id}:
    patch:
      tags:
        - Sites
      summary: Rename a sandbox, or set whether it is permanent
      description: >-
        Send `name` to rename the sandbox (2-80 characters). The rename changes
        the display name only: the slug, sandbox URL, and admin login are
        unchanged. A body containing `name` is treated as a rename and
        `permanent` is ignored.


        Send `permanent` to set whether a sandbox is permanent (never expires).
        Setting `true` requires a paid plan. Setting `false` on a
        currently-permanent sandbox reverts it to the default temporary lifespan
        starting now. A no-op call (value already matches) succeeds without
        changing `expiresAt`.
      operationId: updateSite
      parameters:
        - $ref: '#/components/parameters/SiteIdParam'
        - $ref: '#/components/parameters/WorkspaceHeaderParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 80
                permanent:
                  type: boolean
              anyOf:
                - required:
                    - name
                - required:
                    - permanent
      responses:
        '200':
          description: The updated sandbox.
          content:
            application/json:
              schema:
                type: object
                required:
                  - site
                properties:
                  site:
                    $ref: '#/components/schemas/Site'
        '400':
          description: >-
            `invalid_request` — neither `name` nor a boolean `permanent` was
            sent. `invalid_site_name` — `name` is shorter than 2 or longer than
            80 characters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: '`permanent_requires_paid_plan` — `permanent: true` on a free plan.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: >-
            `forbidden` — the sandbox is guest-owned; guest sandboxes cannot be
            made permanent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/SiteNotFound'
        '409':
          description: '`site_not_available` — the sandbox is not `creating` or `ready`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  parameters:
    SiteIdParam:
      name: id
      in: path
      required: true
      description: The sandbox id.
      schema:
        type: string
      example: site_abc123
    WorkspaceHeaderParam:
      name: X-SandyWP-Workspace
      in: header
      required: false
      description: >-
        Selects which of the caller's workspaces to act in, for accounts
        belonging to more than one (legacy alias: `X-SandyWP-Organization`).
        Defaults to the caller's own personal workspace when omitted. Every id
        in this API (sandboxes, Templates, repositories) is scoped to a single
        workspace, so this header changes which set of resources is visible.
      schema:
        type: string
  schemas:
    Site:
      type: object
      description: >-
        The owner-facing view of a sandbox. Strips internal provisioning details
        (container/DB names, password hash, warm-pool ids, routing) and keeps
        only what a caller needs to use and log into the sandbox.
      required:
        - id
        - name
        - slug
        - status
        - publicUrl
        - wordpressVersion
        - phpVersion
        - workerCode
        - adminUsername
        - adminPassword
        - magicLoginUrl
        - permanent
        - expiresAt
        - expirationMode
        - idleTimeoutMinutes
        - createdAt
        - readyAt
        - failureReason
      properties:
        id:
          type: string
          example: site_abc123
        name:
          type: string
          example: my-sandbox
        slug:
          type: string
          example: my-sandbox
        status:
          $ref: '#/components/schemas/SiteStatus'
        publicUrl:
          type: string
          format: uri
          example: https://my-sandbox.sandywp.dev
        wordpressVersion:
          type: string
          description: >-
            The concrete installed version (e.g. `7.0.4`), not the request-time
            alias (e.g. `latest`).
        phpVersion:
          type: string
        workerCode:
          type: string
          example: eu1
        adminUsername:
          type: string
          example: admin
        adminPassword:
          type:
            - string
            - 'null'
          description: >-
            Plaintext admin password. Present here and on single-sandbox
            responses; omitted from list responses (see `SiteListItem`) so it
            never rides along on a bulk poll.
        magicLoginUrl:
          type:
            - string
            - 'null'
          format: uri
        permanent:
          type: boolean
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
        expirationMode:
          $ref: '#/components/schemas/DemoExpirationMode'
        idleTimeoutMinutes:
          type:
            - integer
            - 'null'
        createdAt:
          type: string
          format: date-time
        readyAt:
          type:
            - string
            - 'null'
          format: date-time
        failureReason:
          type:
            - string
            - 'null'
    Error:
      type: object
      description: >-
        The standard error envelope returned by every endpoint on a non-2xx
        response.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                A stable, machine-readable error code (e.g. `invalid_request`,
                `site_not_found`, `plan_limit_reached`). See each operation for
                the codes it can return.
              example: invalid_request
            message:
              type: string
              description: A human-readable message. Safe to display to an end user.
            details:
              type: object
              description: >-
                Optional structured context (e.g. `{"plan": "free", "limit": 2}`
                for a plan-limit error). Omitted entirely on the generic
                internal-error fallback; `{}` when an error has no extra
                context.
              additionalProperties: true
    SiteStatus:
      type: string
      enum:
        - creating
        - ready
        - expired
        - failed
        - pending_delete
        - deleted
        - cleanup_failed
    DemoExpirationMode:
      type: string
      description: >-
        `fixed` expires a demo/idle-mode sandbox after a fixed lifetime from
        creation/launch; `inactivity` renews the deadline while it is actively
        used.
      enum:
        - fixed
        - inactivity
  responses:
    Unauthorized:
      description: >-
        No valid bearer token was presented, or a scoped OAuth token was used
        outside its allowed Imports and account-identity routes.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: auth_required
              message: A logged-in user is required.
              details: {}
    SiteNotFound:
      description: >-
        No such sandbox, or it is not visible to the caller. Ownership failures
        are intentionally indistinguishable from a missing sandbox.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: site_not_found
              message: Site not found.
              details: {}
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Personal API token, sent as `Authorization: Bearer <SANDYWP_API_KEY>`.
        Obtain one from the dashboard account menu (API keys), `sandywp auth
        login`, or `POST /api/account/tokens`. A missing or invalid token
        returns `401 auth_required`. Scoped OAuth tokens are limited to the
        Imports family and `GET /api/account/me`; see the top-level conventions.

````