# Jaws Deploy > Jaws Deploy is a deployment automation (continuous delivery) platform. A CI tool builds and packages the code; Jaws Deploy turns the build artifact into a versioned release and deploys it to Windows and Linux machines and Azure services, promoting it through environments such as Development, Staging and Production. It runs as a managed cloud service (EU-hosted) or self-hosted (Jaws Deploy Stack). Key facts for agents: - Web app and sign-up: https://app.jawsdeploy.net. A free plan is available; current plans and limits are on the pricing page. - REST API base URL: `https://app.jawsdeploy.net/api`. Auth is HTTP Basic with a service account: `Authorization: Basic base64(serviceAccountId:apiKey)`. JSON in and out. Errors return `{ errorcode, error }`. - The OpenAPI document describes every REST API endpoint, parameter and response - see REST API below. - MCP server: `https://app.jawsdeploy.net/mcp` (Streamable HTTP, same Basic auth). Works with Claude Code, Claude Desktop, Cursor, Codex and any MCP client. Deployments need a dry run (`preview_deploy`) before `trigger_deploy`; secret variables cannot be read or written through MCP. - Self-hosted Stack users: replace `app.jawsdeploy.net` with your own host. - Deployment scripts run in PowerShell 7, Windows PowerShell 5.1 or Python 3. Runtimes are provisioned on each target by the Jaws Deploy Agent. - Variables use `#{VariableName}` syntax and are scoped by environment, machine, tag, step and more. - CI integrations: TeamCity plugin, PowerShell SDK, Python SDK, or plain REST calls from GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, CircleCI. - Uses the same concepts as Octopus Deploy (projects, steps, step templates, lifecycles, channels) and has a migration guide. - Core concepts: workspace, project, step, step template, script module, environment, machine/target, tag, variable, release, channel, lifecycle, deployment, package feed. - Every documentation page listed here is also available as Markdown by adding `.md` to its URL. This file is the full text of the Jaws Deploy documentation in one place. A shorter index with links is at https://www.jawsdeploy.net/llms.txt, and every page is also available on its own as Markdown. # Glossary ## API Key A credential issued to a service account that grants access to the Jaws Deploy REST API. API keys are included in the `Authorization` header using Basic auth format (service account ID and key pair). Keys can be rotated or revoked independently in workspace settings without affecting other integrations. ## Deployment The execution of a release against an environment. When a release is deployed, Jaws Deploy resolves the environment-scoped variables, identifies the matching targets, and runs each step in the deployment process in order. Every deployment is recorded: step status, duration, log output per target, and the overall outcome. ## Deployment Process The ordered list of steps that runs when a project's release is deployed to an environment. The same process is used for every environment in the lifecycle; environment-scoped variables supply the differences between runs. Individual steps can be restricted to specific targets, tags, or environments, so parts of the process only execute where they are needed. ## Environment A named deployment tier such as Dev, Staging, UAT, or Production. Environments group the targets a release will deploy to and determine which variable values are used during a deployment. Environments are ordered within a lifecycle, defining the promotion path a release follows on its way to production. ## Jaws Deploy Agent A lightweight background service installed on a deployment target. The Jaws Deploy Agent connects to the server, receives deployment instructions, executes steps locally, and streams log output back. The agent runs on Windows and Linux and supports offline installation for air-gapped environments. A target must have a connected, registered agent before it can receive deployments. ## Lifecycle A sequence of environments defining how a release moves from creation to production. Each phase in a lifecycle can be mandatory or optional and can require manual approval or advance automatically when the previous phase completes. A project references one lifecycle. Every release that project creates follows the same promotion path. ## Output Variable A value produced by a deployment step and made available to later steps in the same deployment. A script step records an output value with `Set-JawsOutputValue` in PowerShell or `jaws.set_output_value(...)` in Python; later steps read it back through a variable reference such as `#{OUTPUT.Step..Global.}`. Output variables pass generated values — a deployed URL, a resource ID, a computed version — between steps without a database or temporary file. A value can be scoped to the machine that produced it or shared globally across the whole deployment. ## Package A versioned deployment artefact containing the application code, binaries, or scripts to be deployed. Packages are typically zip archives, tarballs, or NuGet packages. A release references specific package versions. Steps in the deployment process download and extract those packages to deployment targets. ## Package Feed A storage registry for versioned deployment packages. Jaws Deploy includes a built-in feed so teams do not need a separate artefact server; external feeds (NuGet repositories, Azure Artifacts, Amazon S3, and others) can also be registered alongside it. Feeds are configured at the workspace level and referenced by individual deployment steps. ## Project A named deployable unit — typically an application, API, service, or batch job — that owns a deployment process, a set of project-scoped variables, and a full history of releases and deployments. Projects deploy to environments through a lifecycle. The deployment process defines the ordered steps that run each time a release reaches an environment. ## Project Folder A named container for organizing related projects within a workspace. Project folders are purely organizational — they do not affect variable scoping, lifecycle assignment, or deployment behaviour. Teams with many projects use folders to keep the project list manageable. ## Release An immutable snapshot of the packages, variables, deployment process, and scripts that existed at a specific point in time. Once created, a release does not change — the same release is promoted through each environment in the lifecycle. This immutability means what was tested in Staging is identical to what is promoted to Production: no rebuild, no reassembly. ## Rolling Deployment A deployment that reaches machines in a controlled order and a controlled number at a time, rather than in whatever order and concurrency the step happens to allow. Three controls shape it: the **window** (how many machines run the step at once - *parallel machines* on a step, defaulting to 1), the **machine order** (by machine name, or by tag priority so a canary goes first), and the **barrier** (finish every machine of one tag rank before the next rank starts). All three are per step. A rolling step rolls one step across the fleet; a rolling group rolls several consecutive steps, so one machine completes all of them before the next machine starts any. ## Rolling Group A run of consecutive steps that one machine completes before the next machine starts any of them. It is the difference between rolling a step and rolling a rollout. Consider the four actions most deployments are made of: stop service, deploy package, start service, smoke test. As four separate steps, step 1 stops every machine before step 2 deploys anything - the per-step window controls the rate, not the sequence. As a rolling group, one machine stops, deploys, starts and smoke-tests before the next machine begins, so only one machine is out of service at a time. A group is a label over consecutive steps rather than a step itself. Its members keep their own machine filters, so a member that does not target a machine is recorded as *not targeted* there; grouping changes a rollout's structure, never a step's reach. ## Script Module A shared library of helper functions that script steps across all projects can import. Script modules centralise utility functions — logging helpers, connection wrappers, validation routines — in one versioned location rather than copying code into every script step. Modules are written for a specific runtime, so PowerShell steps import PowerShell modules and Python steps import Python modules. ## Script Runtime A managed language runtime that Jaws uses to execute script steps and script modules. Jaws supports PowerShell (PS7+ and PS5.1) and Python, provisioning and caching the required runtime on each target so scripts behave the same on every machine without a manual install. Each script step selects its runtime, and script modules share helper code within a runtime. The agent downloads and caches runtimes per workspace and supports offline provisioning for air-gapped targets. ## Secret A variable whose value is treated as sensitive. Secret values are encrypted at rest, redacted in deployment logs, and never returned in plaintext through the API or UI after they are saved. Secrets follow the same scoping rules as regular variables, so environment-specific credentials stay isolated between tiers. ## Service Account A system identity used by CI tools, scripts, and automated workflows to authenticate against the Jaws Deploy REST API. Each service account is issued an API key and carries permissions scoped to specific projects or an entire workspace. Service accounts are the recommended integration mechanism for pipelines in TeamCity, GitHub Actions, GitLab CI, and similar tools. ## Step A single unit of work within a deployment process. Steps can run built-in actions (deploying a package, configuring IIS, applying a database migration), step templates shared across projects, or inline scripts in PowerShell or Python. Steps are executed in order. Each step can be restricted to run only on specific targets or in specific environments, can carry variable values scoped to just that step, and can emit output variables for later steps to consume. ## Step Template A reusable, versioned step definition stored at the workspace level that any project can add to its deployment process. Step templates standardise common deployment tasks — installing a Windows service, sending a notification, running a health check — so the same logic is not duplicated across many projects. Changes to a template can be pushed to all projects that reference it. ## Tag A label applied to deployment targets to group them by role, region, or purpose. Steps in a deployment process can be restricted to run only on targets carrying a given tag — for example, only on machines tagged `web` or `db-primary`. Tags make it possible to model complex multi-machine deployments without creating a separate step per machine. ## Target A machine, cloud service, or endpoint that receives a deployment. Targets can be Windows or Linux servers running the Jaws Deploy Agent, cloud resources (such as Azure App Service, AWS Lambda, or Kubernetes workloads), or other types supported by step templates. Targets are registered to environments and can be labelled with tags, letting steps in the deployment process run only on a specific subset of machines. ## Variable A named configuration value resolved at deployment time. Variables can be scoped to a workspace, project, environment, target, tag, or an individual deployment step, so the same deployment process naturally picks up different connection strings, service endpoints, or feature flags in each environment. When several scopes match, the most specific value wins — a step-scoped value beats a target-scoped one, which beats a tag, which beats an environment. Values are substituted into scripts, configuration files, and step properties during a deployment run. Steps can also produce output variables that later steps consume. ## Workspace The root organizational container in Jaws Deploy. A workspace holds all projects, environments, lifecycles, package feeds, targets, variables, and service accounts for a team or business unit. Teams start with one workspace. Organizations with separate products or clearly divided operational boundaries may run multiple workspaces to keep access, billing, and configuration isolated. # Guides ## What is Jaws Deploy? Source: https://www.jawsdeploy.net/guides/what-is-jaws-deploy | Section: Getting Started Jaws Deploy is a deployment automation platform that turns built artifacts into repeatable releases across environments. Jaws Deploy is a deployment automation platform. It takes the build artifact your CI server already produced and walks it through your environments - Dev, Staging, Production, customer-specific tiers - in a controlled, repeatable, auditable way. The distinction matters. CI systems are good at building and testing code. They are not designed to model environments, scope variables by deployment context, or replay the same deployment plan against ten machines with consistent behaviour. Jaws Deploy is. > **// Mental model - Build once. Deploy many times.** > > Your CI tool produces an artefact and hands it off. Jaws Deploy creates an immutable release from that artefact and runs the same release through every environment without rebuilding it. The release that passed Staging is byte-for-byte the same release that reaches Production. ### What it actually does A project in Jaws Deploy owns a **deployment process** - an ordered list of steps that runs against an **environment**. Each environment is wired to one or more **targets**: machines running the Jaws Deploy Agent, or managed cloud services. Steps resolve **variables** scoped to the current environment, target, or tag. The shape stays the same regardless of scale. The same model handles a single Windows box hosting an IIS site and a hundred-target fleet across regions. ### The objects you'll work with - **Project**: A deployable unit with its own process, variables, and release history. - **Environment**: A deployment stage like Dev, Staging, or Production. - **Target**: A machine or cloud service that receives the deployment. - **Release**: An immutable snapshot of packages, variables, and steps. ### Who it's for Teams that ship to multiple environments and want the deployment story to be a structured, replayable plan rather than a CI script that grew teeth. Common shapes: small DevOps teams running Windows or mixed stacks, agencies repeating deployments across client environments, regulated organisations that need an auditable record of what reached production. ### Where it sits in your stack Jaws Deploy lives between your CI system and your infrastructure. CI keeps doing builds and tests. Jaws Deploy owns the deployment lifecycle: release creation, promotion through environments, variable resolution, target execution, and history. ## Getting Started with Jaws Deploy Source: https://www.jawsdeploy.net/guides/getting-started | Section: Getting Started The shortest path from a fresh account to a working deployment, in under fifteen minutes. This guide walks through the first deployment end-to-end. The goal is to have a real package deployed to a real target by the end of the read - not a hello-world, but something close to the structure of a production setup. ### Five minutes of prep - A Jaws Deploy Cloud workspace (sign up free, no card required). - One target: a Windows or Linux machine you can install the agent on, or an Azure Web App. - A build artefact - any zip, tarball, or NuGet package will do. ### 1. Create a workspace A workspace is the root container. It holds projects, environments, lifecycles, feeds, targets, variables, and the team. After signup you land in an empty workspace with the default lifecycle `Dev -> Staging -> Production` already wired up. ### 2. Add an environment and a target Go to **Infrastructure -> Environments**, open `Dev`, and click **Add target**. Two choices: **Install the agent** On a Windows or Linux machine you control. Run the installer, paste the registration command, the agent connects outbound. The target appears in the environment within seconds. **Add a cloud target** For Azure Web Apps, register the App Service through the Azure connection wizard. No agent install needed - the platform talks to the Azure management API directly. ### 3. Create a project **Projects -> New project**. Give it a name. The project starts with an empty deployment process. Add one step - pick **Deploy a package** from the template list, point it at a package in the built-in feed (you can drag-and-drop a zip into the feed UI), and save. ### 4. Create and deploy a release On the project page, click **Create release**. Jaws Deploy snapshots the deployment process, the package version, and the variable definitions into a release - a numbered, immutable record. Then click **Deploy** and pick `Dev`. The live log opens. You'll watch the agent receive the work, extract the package, run any post-deploy hooks, and report success. > **// What just happened - You modelled an environment, registered a target, defined a deployment process, created a release, and ran a deployment.** > > From here, every additional environment is incremental - register more targets, scope variables to them, and promote the same release. The setup work is largely done. ### Next steps With the basics working, the natural follow-ups are: connect your CI tool to create releases automatically, add a second environment and promote releases through the lifecycle, and replace inline values with scoped variables so the same process serves multiple environments cleanly. ## Jaws Deploy vs. Traditional Deployment Tools Source: https://www.jawsdeploy.net/guides/jaws-deploy-vs-traditional-deployment-tools | Section: Getting Started How a deployment platform differs from CI scripts, FTP uploads, and hand-rolled PowerShell. Most teams reach a deployment platform after their initial setup stops scaling. The story is usually the same: a single deploy script worked fine for one environment, started copying itself for the second, and developed three subtly different forks by the time the third one shipped. ### What "traditional" deployment usually means In practice it's one of three shapes. Each works for a while and breaks the same way. **CI does it all** Build and deploy in the same pipeline. The deploy step is a script in the repo. Works until you have environment-specific values and someone hardcodes prod credentials in the YAML. **Shell scripts on the build server** Build artefacts get rsync'd or zipped, then a script SSHes into the target and copies them over. Works until rollback is needed, or until two engineers edit the script in parallel. **Manual deployment** FTP, RDP, copy-paste, a checklist. Works at very small scale, becomes the source of every Friday-night incident at any larger scale. ### What a deployment platform replaces A deployment platform like Jaws Deploy is not faster than a shell script. It's structured. The trade is: more upfront setup, far less ongoing maintenance, and a deployment record that survives the engineer who wrote the original script. ### The shifts that earn the install - **Environments become real objects** with their own variable scope, lifecycle position, and history - not just folder names in a build script. - **Releases are immutable.** The thing that reached Staging is the same thing that reaches Production. No rebuild, no drift. - **Variables resolve by scope** instead of being read from one large config file. Connection strings live with the environment, not the script. - **Deployment is observable.** Each step has live logs, status, and timing. Failures point at a specific step, not the whole job. - **History is structured.** You can answer "what changed between Tuesday's deploy and Friday's" without digging through CI logs. > **// When a platform is overkill - Don't reach for this on day one of a side project.** > > If you ship to one environment, from one developer's machine, with one config file - you do not need a deployment platform. The line is usually drawn at "two or more environments, plus people other than the original author touching deployments." ### The cost side The honest part: a deployment platform is another piece of software to learn, install (if self-hosted), and operate. The pay-off is real but back-loaded. Teams that adopt one report the inflection point at around the third environment or the second engineer joining the rotation. ## The Role of Jaws Deploy in CI/CD Workflows Source: https://www.jawsdeploy.net/guides/role-of-jaws-deploy-in-ci-cd | Section: Getting Started CI builds and tests. Jaws Deploy promotes the same release through Dev, Staging, and Production. CI/CD is one acronym hiding two jobs. **CI** - Continuous Integration - is about building and testing. **CD** - Continuous Delivery or Deployment - is about getting the result into environments. Jaws Deploy owns the CD half. **Build and verify** Compile, run unit tests, run integration tests, produce a versioned artefact. CI tools - TeamCity, GitHub Actions, GitLab, Jenkins, Azure Pipelines - are excellent at this. **Release and promote** Create an immutable release from the artefact, resolve variables per environment, execute deployment steps on targets, stream live logs, and record what happened. ### The hand-off A successful CI build calls into Jaws Deploy to create a release. The call carries three things: the project, the package version, and any release notes. Jaws Deploy snapshots the current deployment process and variables into a numbered release. From there, CI's job is over. Promotion - Dev to Staging to Production - is owned by Jaws Deploy, driven from the UI, the REST API, or the PowerShell SDK. ### How CI typically triggers a release A single PowerShell step at the end of the build configuration. Same pattern works for GitHub Actions, GitLab, Jenkins, and Azure Pipelines. ``` Connect-JawsDeploy ` -Url "https://app.jawsdeploy.net" ` -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "2026.5.17.%build.number%" ` -Packages @{ "Checkout.Web" = "2026.5.17" } ``` > **// The most common mistake - Don't put deployment logic inside the CI pipeline.** > > Teams that paste a `deploy-to-prod.ps1` into their GitHub Actions workflow lose every benefit of having a release model: no immutable snapshot, no variable scoping, no promotion gate, no clean rollback. Let CI build. Let Jaws Deploy deploy. ### Why the split is worth keeping It's tempting to push deployment into CI because CI runs anyway. The cost shows up later: the same Production deployment now lives in YAML, in a script someone wrote two engineers ago, and in everyone's memory of how the rollback worked last time. Splitting build and deploy gives both pieces room to specialise and a clean interface in between. ## Integrating Jaws Deploy with TeamCity Source: https://www.jawsdeploy.net/guides/integrating-teamcity | Section: CI/CD Integration Keep TeamCity focused on building and testing. Let Jaws Deploy handle release creation and environment promotion. TeamCity pairs naturally with Jaws Deploy. TeamCity does builds and tests; the last step of the build configuration creates a Jaws Deploy release. Promotion from Dev to Production is then driven from Jaws Deploy itself. ### Two integration paths Pick whichever fits your TeamCity setup. **The TeamCity plugin** Install the Jaws Deploy plugin in TeamCity. Add a build step "Create Jaws Deploy release" with structured fields for project, version, and package mappings. **PowerShell SDK** Add a PowerShell build step that calls `New-JawsDeployRelease`. No plugin required. Easier to source-control alongside the build configuration. ### Drop this into a final "Create release" build step Use a TeamCity parameter for the API key and pass the build number as the release version. ``` Connect-JawsDeploy ` -Url "https://app.jawsdeploy.net" ` -ApiKey "%env.JAWS_API_KEY%" New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "%build.number%" ` -Packages @{ "Checkout.Web" = "%build.number%" } ` -ReleaseNotes (Get-Content .\CHANGELOG.md -Raw) ``` ### Where the package comes from Two common shapes: - **Push to Jaws Deploy's built-in feed.** Add a NuGet push step (or zip + curl upload) before the release-creation step. The release then references the version you just pushed. - **Reference TeamCity build artefacts directly.** Configure Jaws Deploy with TeamCity as an external feed. The release locks the artefact by TeamCity build number. > **// API key storage - Use a TeamCity parameter, not a literal in the build configuration.** > > Mark the parameter as password-typed so it's masked in logs and not exposed to project-level permissions. The same key can be used across all build configurations that need to publish releases. ## Integrating GitHub Actions Source: https://www.jawsdeploy.net/guides/integrating-github-actions | Section: CI/CD Integration Add one step at the end of your workflow that creates a Jaws Deploy release tied to the commit you just built. Jaws Deploy works with GitHub Actions through the REST API or the PowerShell SDK. The shape is the same as any CI integration: build and test in GitHub Actions, then call out at the end of the workflow to create a release. ### Add at the end of an existing workflow Store the API key in GitHub Actions secrets (`Settings -> Secrets and variables -> Actions`). ``` - name: Create Jaws Deploy release shell: pwsh env: JAWS_API_KEY: ${{ secrets.JAWS_API_KEY }} run: | Install-Module -Name JawsDeploy -Scope CurrentUser -Force Connect-JawsDeploy -Url "https://app.jawsdeploy.net" -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "${{ github.run_number }}" ` -Packages @{ "Checkout.Web" = "${{ github.run_number }}" } ``` ### Using the REST API instead If you prefer not to use PowerShell, the REST API takes the same parameters. Use Basic auth with a service account ID and the API key. ### Same outcome, plain curl ``` curl -X POST "https://app.jawsdeploy.net/api/workspaces/default/projects/checkout-service/releases" \ -u "$JAWS_SA_ID:$JAWS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "version": "'"$GITHUB_RUN_NUMBER"'", "packages": { "Checkout.Web": "'"$GITHUB_RUN_NUMBER"'" } }' ``` > **// Auto-deploy after release creation - Add `-AutoDeploy Dev` to push straight to a deployment after the release is created.** > > This is useful for the first environment in the lifecycle. Promotion to later environments stays manual or gated through Jaws Deploy approvals - keep that surface inside the deployment platform, not in your workflow file. ## Integrating GitLab CI Source: https://www.jawsdeploy.net/guides/integrating-gitlab | Section: CI/CD Integration A small `deploy` stage that hands off the build artefact and version to Jaws Deploy after tests pass. GitLab CI integrates with Jaws Deploy the same way as any other CI tool: a final stage calls the REST API or PowerShell SDK to create a release. Pipeline variables are stored in GitLab's CI/CD settings as masked variables. ### A dedicated deploy stage after build and test Mark `JAWS_API_KEY` as masked in **Settings -> CI/CD -> Variables**. ``` stages: - build - test - release create_release: stage: release image: mcr.microsoft.com/powershell:latest script: - pwsh -c "Install-Module -Name JawsDeploy -Scope CurrentUser -Force" - pwsh -c "Connect-JawsDeploy -Url 'https://app.jawsdeploy.net' -ApiKey '$JAWS_API_KEY'" - pwsh -c "New-JawsDeployRelease -Workspace 'default' -Project 'checkout-service' -Version '$CI_PIPELINE_IID' -Packages @{ 'Checkout.Web' = '$CI_PIPELINE_IID' }" only: - main ``` ### Versioning conventions that work well `$CI_PIPELINE_IID` is the simplest version source - monotonic and unique within the project. If you tag releases, `$CI_COMMIT_TAG` is cleaner. For SemVer schemes, build the version from a base in the repo plus the pipeline ID as the build metadata segment. > **// One job, multiple projects - If your repo builds several deployable units, create one Jaws Deploy release per unit.** > > Use a script that iterates over the project list and calls `New-JawsDeployRelease` for each, or model the GitLab job as a matrix across the project names. Either way: one Jaws Deploy release per deployable unit, not one per repo. ## Integrating Jenkins Source: https://www.jawsdeploy.net/guides/integrating-jenkins | Section: CI/CD Integration Use the PowerShell SDK in a Jenkinsfile, or call the REST API directly from a `sh` step. Jenkins works with Jaws Deploy through PowerShell or shell steps in a declarative or scripted Jenkinsfile. Both rely on a Jenkins credential storing the API key. ### Add this stage at the end of the pipeline Use a `withCredentials` block so the API key is masked in console output. ``` stage('Create release') { steps { withCredentials([string(credentialsId: 'jaws-deploy-api-key', variable: 'JAWS_API_KEY')]) { pwsh ''' Install-Module -Name JawsDeploy -Scope CurrentUser -Force Connect-JawsDeploy -Url "https://app.jawsdeploy.net" -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "$env:BUILD_NUMBER" ` -Packages @{ "Checkout.Web" = "$env:BUILD_NUMBER" } ''' } } } ``` ### Without PowerShell If the Jenkins agents are Linux without PowerShell installed, call the REST API directly with `curl` from an `sh` step. The behaviour is identical. > **// Multi-branch pipelines - Only create releases on the branches that should produce deployable artefacts.** > > Wrap the release stage in a `when { branch 'main' }` block (declarative) or an `if (env.BRANCH_NAME == 'main')` guard (scripted). Otherwise every PR build will attempt to publish a release version that may not deploy cleanly. ## Integrating Azure Pipelines Source: https://www.jawsdeploy.net/guides/integrating-azure-pipelines | Section: CI/CD Integration A clean separation: Azure Pipelines for build and test, Jaws Deploy for release promotion. Azure Pipelines integrates through a PowerShell task at the end of the build pipeline. The API key is stored as a secret pipeline variable or - cleaner - in an Azure DevOps variable group shared across pipelines that publish to the same Jaws Deploy workspace. ### Final stage that creates a Jaws Deploy release Reference the variable group from the pipeline: ```yaml variables: - group: jaws-deploy ``` ``` - stage: Release jobs: - job: CreateRelease pool: vmImage: 'windows-latest' steps: - task: PowerShell@2 displayName: 'Create Jaws Deploy release' env: JAWS_API_KEY: $(JawsApiKey) inputs: targetType: 'inline' script: | Install-Module -Name JawsDeploy -Scope CurrentUser -Force Connect-JawsDeploy -Url "https://app.jawsdeploy.net" -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "$(Build.BuildNumber)" ` -Packages @{ "Checkout.Web" = "$(Build.BuildNumber)" } ``` ### Self-hosted agents The pattern is the same on self-hosted agents - just make sure PowerShell 7 is installed and the agent can reach `app.jawsdeploy.net` (Cloud) or your Stack URL on the network. If the network is restricted, allow outbound HTTPS to those hosts. > **// Why not Azure Release Pipelines? - Azure Release Pipelines and Jaws Deploy solve the same problem.** > > If you're already heavily invested in Azure Release Pipelines, that's fine - stick with it. Teams typically choose Jaws Deploy when they want the deployment platform to live outside Azure DevOps, want Windows + Linux + cloud target uniformity, or want a smaller surface to operate. ## Integrating CircleCI Source: https://www.jawsdeploy.net/guides/integrating-circleci | Section: CI/CD Integration Add a CircleCI job that promotes successful builds into Jaws Deploy and starts a deployment. CircleCI talks to Jaws Deploy through the REST API. CircleCI doesn't ship PowerShell by default, so the cleanest path is a Linux job that calls `curl`. ### A `create_release` job after build and test Store `JAWS_API_KEY` and `JAWS_SA_ID` as project environment variables. ``` version: 2.1 jobs: create_release: docker: - image: cimg/base:stable steps: - run: name: Create Jaws Deploy release command: | curl -X POST "https://app.jawsdeploy.net/api/workspaces/default/projects/checkout-service/releases" \ -u "$JAWS_SA_ID:$JAWS_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"version\": \"$CIRCLE_BUILD_NUM\", \"packages\": { \"Checkout.Web\": \"$CIRCLE_BUILD_NUM\" } }" workflows: build_and_release: jobs: - build - create_release: requires: [build] filters: branches: only: main ``` > **// Triggering a deployment too - Add a second API call to start a deployment to `Dev`.** > > After release creation, POST to `/api/.../releases/{version}/deployments` with `{ "environment": "Dev" }`. That keeps the staging deploy automatic and leaves later environments gated inside Jaws Deploy. ## Connect your AI assistant to Jaws in 3 minutes Source: https://www.jawsdeploy.net/guides/mcp-quickstart | Section: CI/CD Integration Step-by-step setup for Claude Code, Claude Desktop, Cursor, Codex Desktop, and Codex CLI. Use your existing AI subscription to drive Jaws — create projects, build deployment pipelines, read deploy logs, author step templates, set variables. ### Before you start You need: - A Jaws workspace you can manage. Every plan includes the MCP server. - An MCP-aware AI client: - **Claude Code** (v1.0+) - **Claude Desktop** (1.0+, HTTP MCP) - **Cursor** (0.42+) - **Codex Desktop** (Streamable HTTP) - **Codex CLI** (`[mcp_servers]` support) - **Continue** (v0.9.200+) - Anything that follows the [MCP spec](https://modelcontextprotocol.io) - Five minutes. You do **not** need an Anthropic, OpenAI, or any other AI API key on the Jaws side. Your AI client uses your existing subscription. ### Create a Jaws service account 1. In Jaws, open **your workspace → Settings → Service Accounts**. 2. Click **New service account**. Name it something like `mcp-claude-code` — this name appears in audit logs. 3. Grant the service account the workspace permissions you want the AI to have. `Workspace: Full control` is the simplest starting point — scope it down later. 4. Generate an **API key**. Copy both the **service account ID** and the **API key** — you'll need both in the next step. The key is shown once — regenerate if lost. > **Why a service account?** Scoped permissions, independent revocation, and clear audit-log attribution — the AI's actions show as coming from this account, not your personal login. ### Build the Authorization header The MCP server uses HTTP Basic auth. Base64-encode `serviceAccountId:apiKey` and prepend `Basic `. **macOS / Linux:** ```bash echo -n 'YOUR_SERVICE_ACCOUNT_ID:YOUR_API_KEY' | base64 ``` **Windows PowerShell:** ```powershell [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('YOUR_SERVICE_ACCOUNT_ID:YOUR_API_KEY')) ``` The result is your Authorization header value: `Basic `. ### Configure your AI client Adjust the URL if you are on self-hosted Jaws Stack. #### Claude Code Add to `~/.claude/mcp.json`: ```json { "mcpServers": { "jaws": { "type": "http", "url": "https://app.jawsdeploy.net/mcp", "headers": { "Authorization": "Basic YOUR_BASE64_HERE" } } } } ``` Restart Claude Code. Run `/mcp` to confirm `jaws` is listed. #### Claude Desktop Open **Settings → Developer → Edit Config**, add the same `jaws` block under `mcpServers`, restart. #### Cursor Add to `.cursor/mcp.json`: ```json { "mcpServers": { "jaws": { "url": "https://app.jawsdeploy.net/mcp", "headers": { "Authorization": "Basic YOUR_BASE64_HERE" } } } } ``` #### Codex Desktop Open **Settings → MCP servers → Add server**, switch to **Streamable HTTP**, enter the URL and the Authorization header. #### Codex CLI Add to `~/.codex/config.toml`: ```toml [mcp_servers.jaws] url = "https://app.jawsdeploy.net/mcp" [mcp_servers.jaws.headers] Authorization = "Basic YOUR_BASE64_HERE" ``` #### Continue Add to `~/.continue/config.json` under `experimental.modelContextProtocolServers`: ```json [{ "transport": { "type": "http", "url": "https://app.jawsdeploy.net/mcp", "headers": { "Authorization": "Basic YOUR_BASE64_HERE" } } }] ``` ### Verify the connection In your AI client, type: > *Use the Jaws MCP server's `whoami` tool.* Expected response: ```json { "serviceAccountId": "sa_01HXXX...", "organizationId": "org_01HXXX...", "workspaceCount": 3, "connected": true } ``` If `connected: true` and `workspaceCount` matches, you're done. If you get an auth error: check the base64 has no extra whitespace, the service account is active, and the URL ends in `/mcp`. ### What to try first **1. Tour your workspace:** > *List my Jaws workspaces, then pick the production one and list its projects.* **2. Diagnose a failure:** > *Find the most recent failed deployment in my workspace and explain what went wrong.* **3. Build a new pipeline:** > *Create a project called "deploy-api" in workspace ID. Add a step that downloads an artifact zip and copies it to the deploy folder.* **4. Author a step template:** > *Create a step template called "Wait for healthy" that pings an HTTP endpoint until it returns 200 or 2 minutes pass.* **5. Set variables in bulk:** > *Set API_BASE_URL, LOG_LEVEL, and FEATURE_FLAGS in the staging workspace.* **6. Organise by conversation:** > *Create a folder called "Staging" and move all projects whose names contain "staging" into it.* ### What the AI can and can't do - **No deploy triggers without a preview.** `trigger_deploy` only accepts a short-lived token from `preview_deploy`. You always see the dry-run first. - **No secrets written or read in plaintext.** `set_variable` rejects Secret type. `list_variables` masks Secret values as `***`. - **Only what the service account can see.** Permissions are checked on every call via the same `WorkspaceGuard` as the REST API. - **Instant revocation.** Delete or disable the service account and the AI loses access immediately. > **Next - See everything the MCP server can do** > > The [feature overview](https://www.jawsdeploy.net/features/mcp) lists all available tools, explains the safety design, and covers what is on the roadmap. ## Projects and Deployment Logic Source: https://www.jawsdeploy.net/guides/projects-and-deployment-logic | Section: Projects, Steps & Targets A project bundles a deployment process, variables, lifecycle, and release history into one unit. A project is the unit of deployment in Jaws Deploy. It typically maps to a single application, service, or batch job - the smallest thing you would deploy on its own. A project owns four things and references a fifth. ### What a project contains - **Deployment process**: Ordered steps that run when a release is deployed to an environment. - **Variables**: Project-scoped values, inherited and overridden at finer scopes (environment, target, tag). - **Lifecycle reference**: Which lifecycle releases follow - Dev/Staging/Prod, or any other shape. - **Release history**: Every release ever created from this project, with deployment outcomes per environment. ### The deployment process The process is the active part of a project. It's a list of steps, in order. Each step has a type (deploy a package, run a script, restart a service, etc.), a configuration, and a target scope (which environments and which tags it applies to). The same process runs against every environment. Differences between Dev, Staging, and Production come from variables, not from forked processes. > **// One process, many environments - Resist the urge to create separate processes per environment.** > > If your Dev and Prod processes have diverged, the platform stops giving you the "this release passed Staging" guarantee. Use variables for environment differences. Use step scoping ("this step only runs in Prod") for the genuinely environment-specific work. ### How projects relate to other objects Projects live inside a **workspace**. A workspace can hold many projects, all sharing the same environments, lifecycles, feeds, and targets. Project-level isolation comes from variable scoping and permissions, not from running multiple workspaces. Larger organisations sometimes run multiple workspaces (one per product line, one per regulated tenant), but the default is one workspace per team. ### When to split into multiple projects Split when two units have independent release cadences, different deployment processes, or genuinely separate codebases. Don't split when two things ship together - keeping them in one project means one release, one deployment, one rollback boundary. ## Project Steps Explained Source: https://www.jawsdeploy.net/guides/project-steps-explained | Section: Projects, Steps & Targets Steps are not arbitrary scripts. They are typed actions with target scoping, retry, and structured outputs. A step is the unit of work in a deployment process. A step has a **type** (what kind of action), a **configuration** (the inputs to that action), and a **scope** (which environments and which targets it applies to). ### Step types Most deployment processes use a mix of built-in step types and custom script steps. ### Common step types - **Deploy a package**: Extract a package onto a target, apply config transforms, run pre/post hooks. - **Run a script**: Inline PowerShell or Python with access to scoped variables and script modules. - **Manage a Windows service**: Install, restart, stop, or reconfigure a Windows service. - **Configure IIS**: Create or update an IIS site, app pool, or binding. - **Deploy to Azure Web App**: Push a package to an Azure App Service through the management API. - **Custom step template**: A reusable step type defined at the workspace level with structured inputs. ### Step scope Each step has a scope - which environments it runs in, and which targets (or target tags) it targets. The same deployment process can have a step that runs everywhere, a step that only runs on `role:db` targets, and a step that only runs in Production. The platform resolves the scope at deployment time. > **// Order matters - Step order is policy, not decoration.** > > Moving the database migration before or after the package deploy changes what "works" looks like during a partial failure. Treat the step order as a design decision, not a UI detail. ### Parallelism and target fan-out When a step targets multiple machines, the platform runs the work on all of them in parallel and waits for the slowest to finish before moving to the next step. That's the unit of synchronisation - a step gate. If you need stricter ordering across targets (e.g. canary one machine first), split the step into two with different target scopes. ### Failure behaviour A failing script does not fail its step. A non-zero exit code, or a terminating PowerShell error, is reported back by the agent as an error recorded against the step - the step itself is still reported as completed, and the deployment status is unaffected. What moves is the error count. The **error action** of the step decides how the failure is counted. **Stop** logs it as an error, so it counts. **Continue** logs it as a warning, so it does not. Neither value ends the deployment. Subsequent steps still run. Only the **execute condition** of a step can hold it back, and the one that reacts to earlier failures is **All previous steps succeeded** - it skips the step when the steps before it recorded any errors. A step held back this way is recorded as skipped, not failed. A step is reported as failed only when the platform could not run it: the agent could not be reached, a package or script module could not be delivered to it, or the step timed out. That is what turns the deployment itself to Failed, along with a cancellation or a run that outlives its maximum duration. So to judge whether a deployment did what you wanted, check three things: the status is Completed, the error count is zero, and the log shows the step reaching the targets you expected. A step whose filters match no targets is recorded as finished and adds no errors, so a deployment can complete cleanly having done nothing. Re-deploying is a fresh run of the same release rather than a resume - there is no restart from the failed step. Steps that have already done their work can be left out of the new run. ## Step Templates in Action Source: https://www.jawsdeploy.net/guides/step-templates-in-action | Section: Projects, Steps & Targets Step templates eliminate the slow drift between deployment processes that should behave the same way. A step template is a step type you define once and reuse across projects. It has typed inputs, a body (usually PowerShell), and a version number. Projects use the template by selecting it in the step picker and filling in the inputs. ### When you actually want one The trigger is simple: when the same script has been pasted into three projects, or when a new project asks "how do you deploy a Windows service here?" and the answer involves opening another project's deployment process to copy from. That's the moment to promote it to a template. ### What a `Deploy-WindowsService` template looks like The inputs become form fields when a project uses the template. ``` # Inputs: # ServiceName (string, required) # PackageName (package, required) # StopTimeoutSec (number, default 30) # RunAsUser (string, optional, sensitive) param($ServiceName, $PackagePath, $StopTimeoutSec, $RunAsUser) Stop-Service $ServiceName -Force -Timeout $StopTimeoutSec Expand-Archive -Path $PackagePath -DestinationPath "C:\Services\$ServiceName" -Force if ($RunAsUser) { sc.exe config $ServiceName obj= $RunAsUser } Start-Service $ServiceName ``` > **// Versioning - Bump the template version when the behaviour changes.** > > Projects pin to a specific version; they opt in to the new version on their own schedule. That keeps a refactor of the template from silently changing the behaviour of every project that uses it. ### The cultural part Step templates live or die on ownership. A template without a maintainer becomes the source of "I don't know why this fails on Prod but works on Staging" outages. Pick an owner per template. Document the inputs in the help text - that's what the consumer sees. Treat shared deployment code as production code: review, test, changelog. ## Custom Script Modules Source: https://www.jawsdeploy.net/guides/custom-script-modules | Section: Projects, Steps & Targets Script modules are the function library your inline scripts wish they had. A script module is a PowerShell module managed by Jaws Deploy that any script step in any project can `Import-Module` and call. It's the right home for helper functions that several scripts share - authentication wrappers, database helpers, notification utilities. **When the consumer fills out a form** Templates are actions with UI. Pick from the step picker, fill the inputs, get a step. **When the consumer writes a script** Modules are libraries. The consumer is writing their own script and wants to not rewrite a function. ### Inside any script step in any project The platform handles distribution and version pinning of the module. ``` Import-Module JawsCommon $token = Get-AzureToken -Scope $Variables['Azure.Scope'] $conn = Get-SqlConnection -Variable 'Db.Conn' $result = Invoke-Migration -Connection $conn -ToVersion $env:ReleaseVersion Send-SlackNotification -Channel "#deploys" -Message "Deployed v$env:ReleaseVersion to $($Octopus.Environment.Name)" ``` ### What belongs in a module Pure functions used in multiple steps. Logging helpers. Notification senders. Authentication wrappers. Database connection helpers. Things you've written more than once in inline scripts and are tired of re-finding the original. > **// What does not belong - Anything project-specific.** > > If a function only makes sense inside one project, leave it in the project's inline script. A module shared across projects must be generic enough that someone unfamiliar with the original use case can read it and understand what it does. ## Python Script Steps Source: https://www.jawsdeploy.net/guides/python-script-steps | Section: Projects, Steps & Targets Jaws Deploy provisions and caches the Python runtime on each target automatically — write deployment logic in the language your team already knows. Not every deployment step belongs in PowerShell. ### How Python steps work Every script step in Jaws Deploy runs under a managed runtime. Python steps work exactly like PowerShell steps: they receive the full deployment context, can read variables and packages, emit output variables, and appear in the deployment log with the same step-level and target-level visibility. The agent provisions and caches the Python runtime on each target automatically — no manual install per machine, no version pinning in CI, and offline provisioning for air-gapped targets. ### Writing a Python step Select **Run Script (Python)** as the step type. The agent runs your script with the `jaws` module pre-imported via a bootstrap wrapper. Use `jaws.parameters`, `jaws.packages`, and `jaws.set_output_value()` the same way PowerShell uses `$Jaws`. ``` import jaws # Read a deployment variable conn = jaws.parameters.get("ConnectionString") # Read a package path pkg = jaws.packages.get("MyApp", {}) extract_path = pkg.get("ExtractedPath", "") # Emit an output variable for later steps jaws.set_output_value("DeployedUrl", f"https://{conn}/health", scope="Global") print(f"Deployed to {extract_path}") ``` ### Script modules Shared helper code lives in **Script Modules** — workspace-level libraries that any script step can import. A Python script module is a regular `.py` module managed by the platform. Any Python step in any project can import it and call its functions. Use script modules to centralise connection helpers, retry logic, logging wrappers, or any function copied more than twice across your deployment processes. ``` # Python script module (defined once at workspace level) # Module: deploy_helpers import requests def wait_for_health(url: str, retries: int = 10) -> bool: for _ in range(retries): try: r = requests.get(url, timeout=5) if r.status_code == 200: return True except Exception: pass return False # --- # In any Python script step — import and call it import jaws import deploy_helpers url = jaws.parameters.get("HealthCheckUrl") if not deploy_helpers.wait_for_health(url): raise RuntimeError(f"Health check failed: {url}") ``` ### Runtime provisioning and air-gapped targets The agent downloads and caches the Python runtime once per workspace. Subsequent deployments reuse the cached version — no network round-trip per step. For targets without internet access, the runtime can be pre-fetched and bundled with the agent install so Python steps work the same in an air-gapped environment as in a cloud-connected one. - Runtime provisioned and cached per target on first use - Subsequent deployments skip the download — cache is reused - Offline / air-gapped targets use pre-bundled runtime packages - Mix PowerShell and Python steps in one deployment process ## Environments and Targets Source: https://www.jawsdeploy.net/guides/environments-and-targets | Section: Projects, Steps & Targets An environment is a stage. A target is where work runs. Tags decide which targets a step touches. Three nouns do most of the work in Jaws Deploy infrastructure modelling: **environment**, **target**, and **tag**. Used with intent they keep the model small even as the deployment surface grows. ### The three nouns - **Environment**: A deployment stage like Dev, Staging, or Production. Variables scope to it. Lifecycles order them. - **Target**: A machine, a cloud service, or any other endpoint that receives a deployment. - **Tag**: A label on targets. Scopes steps and variables. Lets one process serve many target roles. ### Environments are policy An environment isn't just a name - it's a scope. When a release is deployed to Production, the platform resolves every variable through the Production scope, runs every step that opted into Production, and writes the deployment to Production's history. That is why "deploy this release to Staging then to Production" works as a sentence: each environment carries its own resolution context. > **// Anti-pattern - Don't create one environment per machine.** > > Environments are stages of the release lifecycle. Machines are *where the work runs*. Tags are *how you group them*. Keep these three layers separate or the model collapses into spreadsheet thinking. ### Tags as schema Tags are plain strings, but treat them as schema. Pick conventions early - role tags (`role:app`, `role:db`, `role:cache`), region tags (`region:eu-west`), tenancy tags (`tenant:acme`). Then a single deployment process can scope its database step to `role:db`, its package step to `role:app`, and its smoke test to `role:app, region:eu-west`. Reduce the temptation to introduce new noun types as the surface grows. A `region` is a tag. A `cluster` is a tag. A `tier` is a tag. ## Cloud Targets Source: https://www.jawsdeploy.net/guides/cloud-targets | Section: Projects, Steps & Targets When the cloud provider runs the host, the platform talks to its management API instead of a local agent. A **cloud target** is a deployment endpoint that doesn't run a Jaws Deploy Agent because there's no OS to install one on. The most common case is an Azure Web App. The platform talks to the cloud provider's management API directly and ships packages through their deployment mechanism. **When you need an agent** Windows services, IIS sites, custom binaries, anything that has to run *inside* the host. The agent is the bridge. **When you don't** Managed services where the cloud provider runs the host. Less control over the runtime, less to operate, fewer moving parts. ### What's supported The primary supported cloud target type is **Azure Web App** (App Service). Registration is through an Azure connection - a service principal in your Azure tenant scoped to the resource groups you deploy into. From there, Azure Web Apps become first-class targets that environments and tags can reference. ### Trade-offs Cloud targets cost some flexibility. You can't drop a PowerShell step on a cloud target the same way you can on a machine - the runtime is the cloud provider's. Most teams handle this by keeping the cloud target's deployment process small (push the package, run any deployment slots dance) and putting cross-cutting logic in a separate machine-based step that calls the cloud target as an external system. > **// Mixing in one project - Machine and cloud targets coexist cleanly in one project.** > > A common shape: a project with one cloud target for the Azure-hosted web tier and two machine targets for the Windows services that back it. Each step scopes to whichever target type it applies to. ## Managing Machines Source: https://www.jawsdeploy.net/guides/managing-machines | Section: Projects, Steps & Targets Machines join the platform by installing the agent. From there they are registered, tagged, and assigned to environments. A machine is a Windows or Linux server (physical or virtual) running the Jaws Deploy Agent. The agent connects outbound to the control plane, registers itself, and waits for work. From then on, the machine is just another target. ### Registration Agent installation produces a registration command with a one-time token. Run it on the machine and the agent connects, identifies itself, and shows up in the workspace as a new unregistered target. Finish the registration by assigning the target to one or more environments and adding tags. The platform won't deploy to it until an environment claims it. ### Pick these on day one, they're hard to change later - **Role** - `role:app`, `role:db`, `role:cache`, `role:queue-worker`. - **Region** - `region:eu-west`, `region:us-east`. Only if you have more than one region. - **Capacity tier** - `size:large`, `size:small`. Only if some steps need to run only on large machines. - **Tenant** - `tenant:acme`, `tenant:globex`. Only in multi-tenant setups. ### Health A target's health is the agent's connection status plus the outcome of its last deployment. The infrastructure view surfaces both. A disconnected agent shows up red; deployments to the target are blocked until it reconnects. > **// Decommissioning a machine - Don't just shut it down.** > > Unregister the target first - the platform records the decommission, scoped variables can be cleaned up, and any in-flight deployments fail clearly instead of waiting on a vanished agent. Then turn off the machine. ## Azure Web App Deployment Source: https://www.jawsdeploy.net/guides/azure-web-app-deployment | Section: Projects, Steps & Targets From an empty Azure App Service to a deployed release through a Jaws Deploy cloud target. This guide walks through deploying to an Azure Web App through a Jaws Deploy cloud target. Prerequisites: an existing App Service in Azure, a service principal in the Azure tenant with at least Contributor on the App Service's resource group, and a packaged web app you want to deploy. ### 1. Connect your Azure subscription In **Infrastructure -> Cloud accounts**, add an Azure account. Provide the tenant ID, subscription ID, service principal client ID, and client secret. The platform verifies the credentials and lists the App Services it can see. ### 2. Register the App Service as a target Go to **Infrastructure -> Targets** and add an Azure Web App target. Pick the resource group and App Service from the dropdown (populated from the cloud account). Assign it to an environment - say, `Production` - and add a role tag like `role:web`. ### 3. Add a deployment step In the project's deployment process, add a **Deploy to Azure Web App** step. Point it at the package and scope it to `role:web`. The step uses the Azure management API to upload the package and trigger an in-place deployment. If you want zero-downtime, use deployment slots: deploy to a `staging` slot first, run smoke tests in a follow-up step, then swap slots. ### A small follow-up script step after the package deploy Run it scoped to the same target after the package deploy step succeeds. ``` $cloud = Get-JawsCloudAccount -Name "prod-azure" $rg = $Variables['Azure.ResourceGroup'] $site = $Variables['Azure.SiteName'] Switch-AzureWebAppSlot ` -CloudAccount $cloud ` -ResourceGroup $rg ` -SiteName $site ` -FromSlot 'staging' ` -ToSlot 'production' ``` > **// Configuration - Use Azure App Settings for environment-specific runtime config.** > > The Azure Web App step can push App Settings as part of the deployment. Map Jaws Deploy variables to App Settings keys so the environment's values land on the slot. ## Rolling Deployments and Rolling Groups Source: https://www.jawsdeploy.net/guides/rolling-deployments | Section: Projects, Steps & Targets Take one machine out of service, finish with it, put it back, and only then move to the next - with the exact settings for a one-at-a-time rollout, a canary, and stopping when a machine fails. Rolling deployments update machines in controlled batches, allowing you to define the deployment order, stop on failure, and determine how far each machine progresses before the next begins. This guide walks through five tasks for configuring a safe, predictable rolling deployment. If you are deploying a service that handles traffic, start with the first task. > **// The problem a rolling group solves - Four rolling steps still take the fleet down** > > Take the four actions most deployments are made of: stop service, deploy package, start service, smoke test. As four separate steps, each one finishes on every machine it targets before the next begins - so step 1 stops every machine before step 2 deploys anything. Lowering the window changes the rate, not the sequence, and machine order is resolved per step, so `web01` leading step 1 says nothing about step 2. > > A **rolling group** is the fix: `web01` stops, deploys, starts and smoke-tests, and only then does `web02` begin. ### Rolling steps compared with a rolling group On the left every step sweeps the fleet before the next starts. On the right one machine is out of service at a time. ``` Rolling steps Rolling group stop web01 web02 web03 web01 stop > deploy > start > smoke deploy web01 web02 web03 web02 stop > deploy > start > smoke start web01 web02 web03 web03 stop > deploy > start > smoke smoke web01 web02 web03 fleet stopped before the one machine out of first package lands service at a time ``` ### Where these settings live Everything below is on a project's **Steps** tab. There is no feature switch to turn on: a step already has a window and a machine order, and you change them. A rolling group is created by turning an existing step into one, then moving its neighbours in. ### Finding each control - **Create a rolling group**: **Step actions** on the step you want to start from, then **New rolling group from this step**. Add each adjacent step with **Move into '...'**. - **A step's window and order**: Open the step, then the **Rollout** card. - **A step's failure policies**: The **Failure** card on the same step. - **A group's settings**: Click the group's header row in the step list. That opens its own panel: **Rolling group**, **Rollout**, **Failure**, and **Steps in this group**. - **Move a step in or out**: **Step actions** on that row: **Move into '...'**, or **Take out of group**. - **Get rid of a group**: **Dissolve group**, at the bottom of the group's panel. Its steps stay in the project. In the step list a group is drawn as a header row with its members indented underneath, carrying a step count and a caret to collapse it. Dragging moves whole blocks - an ungrouped step, or a group with all its members - so a drag can never split a group. ### 1. Run several steps per machine before moving on Your deployment is the usual four actions and the fleet still goes down. Put the four steps in a rolling group. 1. Make sure the four steps sit next to each other in the step list, with nothing in between. 2. On the first step, open **Step actions** and choose **New rolling group from this step**. 3. On each of the other three, open **Step actions** and choose **Move into '...'**, naming the group you just made. The option only appears on steps that can legally join it. 4. Click the group's header row to open its settings, and give it a name under **Rolling group**. The name appears in the step list, the deployment preview and the logs. 5. Leave **Window size** at its default of 1. That is one machine out of service at a time. 6. **Save group.** ### Defaults worth confirming on the group - **Window size** - 1. Raise it only if you have capacity to spare. - **Deploy to machines in this order** - Machine name, unless you want a canary first. - **When the group fails on one machine** - *Stop, do not start the group on any further machine*. - **When a step in the group fails on one machine** - *Skip the group's remaining steps on that machine*. #### Rules the editor enforces - Members must be consecutive, with no non-member step in between. If one is in the way, the group is rejected and the message names it. - Every member's **Run on** must be *target machines*. A group works by moving from machine to machine, so a step that runs on a worker or drives cloud targets has no machines to roll across and cannot join. - Groups do not nest, and a group holds at least one step. #### What the group takes over Once a step is a member, the group owns the window, the machine order, the order tags, the barrier, and *when this step fails on one machine*. The step's **Rollout** card is replaced by a note naming the group and showing the settings actually in force. Those stored values are not erased - they apply again if the step leaves. Each member keeps its own machine filters, its own *when this step fails*, its own *run after stop*, and its own execute condition, evaluated per machine. > **// Output values - Inside a group, a step sees only the current machine's output** > > A step inside a group sees only the current machine's output from earlier steps in the same group. Once the group finishes, every machine's output from every member is visible to every later step. > > This is a fact about the run rather than a setting: the group works machine by machine, so when `web02` reaches member 2, `web01` may not have started member 1 and the value does not exist yet. Steps *before* a group are unaffected, because they completed on every machine. ### 2. Roll out one machine at a time The deployment is a single action - deploy the package, restart the service - and you want to control how many machines it touches at a time. You do not need a group for this. Open the step and go to the **Rollout** card. Set the window, labelled *Parallel execution - maximum number of machines*, to 1. It is already the default, so confirm it rather than assume it. Leave **Deploy to machines in this order** on **Machine name** for a stable, predictable order. Raise the window to move a few machines at a time instead. It is capped by your organization's `maxParallelMachines` setting, which defaults to 8; a window above the cap is rejected. The window controls how many machines one step touches at a time. It does not change that the step finishes on every machine before the next step starts. If the deployment is more than one action, task 1 is the answer. > **// Common misreading - The Rolling deployment tick box is not what enables any of this** > > It is a preset for the *ordering* controls: ticking it sets the machine order to tag priority and turns the barrier on, and unticking returns to machine-name order with no barrier. It does not change the window and it does not create a group. See task 3, which is where you would want it. ### 3. Deploy to a canary first You want one machine to take the release first and prove it before the rest of the fleet follows. 1. Tag your canary machine, for example `canary`. 2. Open the group's settings from its header row - or the step's **Rollout** card, for a single rolling step. 3. Set **Deploy to machines in this order** to **Tag priority**. 4. In the tag list it reveals, put `canary` first, then any further tags in the order you want them. 5. Tick **Wait between machine groups**, the barrier. A machine's rank is the index of the first tag it carries. Machines run in rank order, and by machine name within a rank, so the order is stable between deployments. Machines carrying none of the tags run last - they are not excluded, since excluding machines is still the job of the machine filter. If the tag list ends up empty, the order falls back to machine name rather than an arbitrary one. With the barrier on, every machine of one tag rank finishes before the next rank starts, so the canary goes alone all the way through the group. On a single step the **Rolling deployment** tick box does steps 3 and 5 in one click; you still pick the tags. Unticking keeps your tag list, so re-ticking restores the choice. A group's panel has the same two controls but no such preset. #### Running a smoke test only on the canary Give the smoke-test member a machine tag filter of `canary` and leave the other members unfiltered. A group's machine set is the union of the machines its members target, and on each machine only the members that target it run. Every machine enters the rollout and runs the other members; only the canary runs the smoke test, and on the rest that column reads `not targeted`. Grouping never widens a step's reach. A step targets exactly the machines it would have targeted on its own, environment scoping is untouched, and deployment-level machine include/exclude still applies ahead of everything else. > **// Watch for - A filter that matches nothing never runs anywhere** > > A member whose filter matches no machine in the group never runs at all. It is visible - the rollout summary shows `not targeted` in every cell of that member's column - but nothing warns you at the moment you save it. Check the deployment preview before you deploy: it lists, machine by machine, exactly which steps will run where. ### 4. Stop the rollout when a machine fails A machine fails part-way through and you do not want the rollout to carry on. Three settings answer three different questions, evaluated inside out. Getting the right one is the difference between stopping the rollout and stopping the deployment. ### The three levels **When a step in the group fails on one machine** Do the group's remaining steps run on this machine? *Skip the group's remaining steps on that machine* is the default; *Carry on with the group's remaining steps* is what a cleanup or notification member needs. On the group's **Failure** card. **When the group fails on one machine** Does the next machine start? *Stop, do not start the group on any further machine* is the default. On the group's **Failure** card. **When this step fails** Does the rest of the deployment run? This is the only one of the three that can stop the deployment. On the member's own **Failure** card. Walk it through. A member fails on `web01`. The first setting decides what happens on `web01` itself. `web01` is now a failed machine, so the second decides whether `web02` starts. The third, set on the member that failed, decides whether the steps *after* the group run. **The most common mistake.** Both group settings are about the rollout - which machines, and which of the group's steps, still run. Neither stops the deployment. To stop the deployment, open the member itself and set **When this step fails** to **Stop the deployment**. The step editor says so on every member. On a single rolling step the equivalents are both on that step's **Failure** card: **When this step fails on one machine** (*Carry on with the other machines* by default, or *Stop, do not start any more machines*) and **When this step fails** (*Continue to the next step* by default, or *Stop the deployment*). #### Rules that hold whatever you set - Cancellation always wins. A cancelled deployment stops between members, not just between steps. - Stopping never kills work in flight. It stops *new* machines and steps from starting; whatever is already running finishes. - **Run after stop** steps still run, members inside a group included. That is where cleanup and notification belong. - A failure is an Error-level log line, which raises the step's error count - and that count, not the status, is what every failure policy reads. > **// Expect this - A failed script reports the deployment as "Completed, with errors"** > > The headline status comes from the monitor status, and a failed script does not produce a failure status - it produces error-level log lines. The error counts are correct and the rollout stops correctly; the headline still says Completed. A step that *times out* does report Failed. If you are checking whether a rollout stopped, read the rollout summary and the error counts rather than the headline. > > Separately, **an unreachable agent aborts the whole deployment**, regardless of any failure setting above. The rollout summary is still written, so the machine left part-way through is named. If a deployment ends abruptly and none of your failure policies explain it, check whether an agent was unreachable. ### 5. Change a rollout setting and have it take effect You changed the group's window size, redeployed, and the deployment ran the old value. A release is a snapshot. When you create one, the group and all its settings are copied into the release alongside the steps, and each release step's membership is rewritten to point at the snapshotted group. Editing the project afterwards does not change what an existing release deploys. So: change the setting on the project, **create a new release**, and deploy that. Redeploying an existing release deploys the settings that release was snapshotted with. ### What you see while it runs The **deployment preview** shows the resolved machine order, the window, the barrier, and for a group a machine-by-machine list of exactly which steps will run where. The **log tree** runs Group to Machine to Step, with machine nodes created up front in resolved order and starting as *queued*, so machines not yet reached are visibly waiting rather than missing. A **rollout order line** names the machines in the order they will run and why that order was chosen. ### The rollout summary Written even when the rollout stops part-way - which is exactly when it matters, because that is when machines are left on mixed versions. ``` Rollout summary - rolling group 'web-rollout' Machine stop-service deploy-package start-service smoke-test web01 ok ok ok ok web02 ok failed skipped skipped web03 not run not run not run not run ``` ### Driving it from the API Every setting in this guide is also reachable over the REST API and from MCP clients. See [List rolling groups](https://www.jawsdeploy.net/rest-api/rolling-groups-list), [Create a rolling group](https://www.jawsdeploy.net/rest-api/rolling-group-create), [Update a rolling group](https://www.jawsdeploy.net/rest-api/rolling-group-update), [Dissolve a rolling group](https://www.jawsdeploy.net/rest-api/rolling-group-delete) and [Move a step in or out of a rolling group](https://www.jawsdeploy.net/rest-api/project-step-rolling-group). Related reading: [Project Steps Explained](https://www.jawsdeploy.net/guides/project-steps-explained) for how steps target machines in the first place, and [Projects and Deployment Logic](https://www.jawsdeploy.net/guides/projects-and-deployment-logic) for where the deployment process sits. ## The Power of Variables Source: https://www.jawsdeploy.net/guides/the-power-of-variables | Section: Variables & Configuration Define a value once, scope it where it applies, and let Jaws Deploy resolve it per deployment. A variable in Jaws Deploy is a named configuration value with a scope. Scopes are workspace, project, environment, target, and tag. At deployment time the platform resolves the value by walking the scope hierarchy from most-specific to least-specific. ### The shape of the win Without scoped variables, teams end up with a `config.production.json`, a `config.staging.json`, and a script that picks one based on `$ENV`. That works until somebody renames an environment, adds a fourth one, or needs a per-region override. Scoped variables flip this. The variable lives in one place. The scope decides which deployment sees which value. ### One variable, three scoped values Jaws Deploy resolves to the most specific matching scope at deployment time. ``` Name: ConnectionStrings.Main Values: scope: project -> "Server=localhost;Database=app" scope: environment=Staging -> "Server=stg-sql;Database=app_stg" scope: environment=Production -> "Server=prd-sql;Database=app_prd" scope: environment=Production, tag=region:eu -> "Server=prd-sql-eu;Database=app_prd" ``` ### Where variables are used Three places: script steps (read as `$Jaws.Parameters["VAR.Name"].Value` in PowerShell, `jaws.parameters["VAR.Name"]` in Python), config files (token-replaced during deployment, as `#{Name}` or `#{VAR.Name}`), and step inputs (any step parameter can take `#{VAR.Name}`). The coverage matters - it means almost no deployment-time value needs to live in CI scripts or version-controlled config files. > **// Secrets - Secret variables are encrypted at rest and redacted in logs.** > > Mark a variable as a secret at creation. Once saved, the value isn't shown in the UI or returned via the API. Secrets follow the same scoping rules as regular variables. ## Variable Resolution Rules Source: https://www.jawsdeploy.net/guides/variable-resolution-rules | Section: Variables & Configuration Specificity wins. Step beats target, target beats tag, tag beats environment, environment beats project, project beats workspace. When a variable has multiple values across scopes, Jaws Deploy resolves by **specificity**. The most specific matching scope wins. ### The specificity order From most specific (highest priority) to least specific: ### First match in this list wins - **Step-specific** value (scope: the deployment step currently running). - **Target-specific** value (scope: exact target). - **Tag-specific** value (scope: tag that the current target carries). - **Environment-specific** value (scope: current environment). - **Project-default** value (scope: project, no other constraints). - **Workspace-level** value (scope: workspace, no other constraints). ### Step-scoped values A variable value can be pinned to one or more **project steps** with a step filter. When that step runs, its value overrides everything else for the same variable - including a target-specific value - and no other step sees it. This is the most specific scope there is. Reach for it when a single step needs a different value than the rest of the process: a longer command timeout for the migration step, a different path for the warm-up step, a feature flag flipped only while one step runs. The variable keeps its name, so you avoid inventing a parallel `ConnectionStringForMigrations` and remembering to use it in exactly one place. > **// Multiple matches at the same level - If two values at the same specificity match, the deployment fails fast.** > > The platform refuses to guess. The most common cause is two tag-scoped values where the current target carries both tags. Fix by adding another scope to one of them, or by removing the ambiguity at the tag level. ### How a real deployment resolves a variable Target `web-prd-eu-01` is in environment `Production` with tags `role:web` and `region:eu`. The deployment process has a `Warm cache` step. ``` Variable: Cache.Endpoint Defined values: - scope: project -> "localhost:6379" - scope: env=Production -> "prod-cache:6379" - scope: env=Production, tag=region:eu -> "prod-cache-eu:6379" - scope: target=web-prd-eu-01 -> "override-cache:6379" - scope: step=Warm cache -> "warmups-cache-eu:6379" Resolution for the 'Warm cache' step on web-prd-eu-01: -> "warmups-cache-eu:6379" # step-specific wins, even over the target value Resolution for any other step on web-prd-eu-01: -> "override-cache:6379" # target-specific wins Resolution for deployment to web-prd-us-01 (no override): -> "prod-cache:6379" # env wins (no tag match for region:eu) ``` ### When to use each scope Project-default for sensible defaults. Environment for the bulk of differentiation. Tag for cross-cutting concerns (region, role). Target only for one-off overrides during incidents - if you find target-scoped values accumulating, you have a tag opportunity hiding in them. Step for the rare case where one step in the process genuinely needs a different value than its neighbours - keep these few and intentional, since a value that only one step can see is easy to forget. ## Nested and Referenced Variables Source: https://www.jawsdeploy.net/guides/nested-and-referenced-variables | Section: Variables & Configuration References use `#{Variable.Name}` syntax and can chain through several layers without copy-paste. Variables can reference other variables. The syntax is `#{Variable.Name}`. References resolve recursively at deployment time, so a chain of references collapses into a single final value before the step runs. ### Three variables, one resolved value Reduces the duplication in connection strings and URLs. ``` Db.Host = "prd-sql-#{Region}.internal" Db.Name = "app_#{Environment}" Db.Connection = "Server=#{Db.Host};Database=#{Db.Name};Trusted_Connection=yes" Region (env=Production) = "eu-west" Environment (env=Production) = "prd" Resolved value of Db.Connection in Production (eu-west): Server=prd-sql-eu-west.internal;Database=app_prd;Trusted_Connection=yes ``` ### Why this matters Without composition, every connection string in every environment is its own variable, and changing the database server name requires editing N values. With composition, `Db.Host` is one variable scoped per environment, and the rest follows. > **// Cycles - The platform detects and rejects cycles.** > > If `A` references `B` and `B` references `A`, the deployment fails with a clear error pointing at the cycle. The same applies to deeper chains - if any path leads back to the starting variable, the resolution stops. ### Token replacement in files The same `#{Variable.Name}` syntax works inside config files. A package deployment step with **substitute variables in files** enabled walks the listed files and replaces tokens before the package is placed on the target. Useful for `appsettings.json`, `web.config`, custom INI files, and similar. ## Using Variable Filters & Substitution Modes Source: https://www.jawsdeploy.net/guides/variable-filters-and-modes | Section: Variables & Configuration Transform values, render conditional configuration, and decide exactly what a deployment should do with unresolved tokens — without surprising existing workspaces. Jaws replaces variable references such as `#{VAR.SiteName}` in deployment scripts, step properties, variable values, and supported files inside packages. Project variables use the `VAR.` prefix; built-in deployment values use `CONTEXT.`. **Extended** syntax lets a reference also transform the value or control whether a block is rendered. This is configured per workspace. Existing workspaces are pinned to **Legacy** during upgrade, preserving the original plain-token behavior. Newly created workspaces default to **Extended** unless their creator explicitly chooses Legacy. ### The two independent workspace settings - **Substitution syntax**: **Legacy** recognizes plain references such as `#{VAR.Name}` only. **Extended** adds filters, conditionals, and corrected escaping and nested-token handling in package files. - **Unresolved tokens**: **Silent** leaves missing tokens in place. **Warn** also logs them. **Strict** fails before unresolved output is used. > **// Recommended rollout - Legacy → Warn → Extended → Strict** > > For an existing workspace, first leave syntax on Legacy and change unresolved tokens to **Warn**. Run representative deployments, define misspelled or missing variables, and escape text that only looks like a token. Update agents used by file-replacement steps. Then switch to Extended. Adopt Strict only after the warning set is clean. ### Configure a workspace Open **Settings → Workspaces**, edit the workspace, and find **Variable substitution**. Choose the syntax and unresolved-token behavior, then save. When creating a workspace in the UI, you can choose Legacy or Extended immediately. The REST API and MCP `create_workspace` tool accept the same optional `variableSyntaxMode` value. Omitting it creates an Extended workspace. A missing or invalid stored setting falls back to Legacy, which is the safe direction. ### Create an Extended workspace explicitly The response includes the selected `variableSyntaxMode`. ``` POST /api/workspace Authorization: Basic Content-Type: application/json { "name": "Production", "slug": "production", "variableSyntaxMode": "Extended" } ``` ### Use the correct variable namespace Use the complete name in deployment scripts, step properties, and variable values: - `#{VAR.SiteName}` reads the project variable named `SiteName`. - `#{CONTEXT.EnvironmentName}` reads the current deployment environment name. - `STEP.` and `OUTPUT.` references keep their corresponding prefixes. The only shorthand is in files processed by the package file-replacement helpers: there, a project variable can be written as either `#{VAR.SiteName}` or `#{SiteName}`. The short form is an alias added specifically for file replacement; it does not apply to scripts, step properties, or nested variable values. Prefer the prefixed form everywhere so a reference remains valid when moved. ### Filter syntax A filter follows the complete variable name after a pipe: ```text #{VAR.VariableName | FilterName argument} ``` Chain filters with more pipes. They run left to right, after nested references in the variable value have resolved: ```text #{VAR.SiteName | Trim | ToLower | Replace "[^a-z0-9-]" "-"} ``` Filter names are case-insensitive. Arguments are separated by whitespace; wrap an argument in double quotes when it contains spaces, a pipe, or a closing brace. A variable whose exact name includes the pipe text wins over filter parsing, preserving unusual existing variable names. ### Normalize text - `ToUpper` — uppercase using invariant culture. `#{VAR.Site | ToUpper}` turns `prod-site` into `PROD-SITE`. - `ToLower` — lowercase using invariant culture. - `Trim` — remove whitespace from both ends. - `Trim start` or `Trim end` — remove whitespace from one end. Other arguments are rejected. ### Replace, slice, truncate, and format - `Replace "pattern" "replacement"` — regular-expression replacement. The replacement is optional; omit it to remove matches. Example: `#{VAR.Build | Replace "[^0-9.]" ""}`. - `Substring length` — take `length` characters starting at index 0. `#{VAR.Value | Substring 3}` turns `abcdefgh` into `abc`. - `Substring start length` — take `length` characters from the zero-based `start` index. Negative or out-of-range values fail the deployment. - `Truncate length` — keep at most `length` characters and append `...` only when truncation occurs. - `Format "format"` — apply an invariant .NET numeric or date format, for example `#{VAR.Price | Format N2}` or `#{CONTEXT.DeploymentDate | Format "yyyy/MM/dd"}`. A value that is neither a number nor a date is left unchanged. ### Prepare a value for its destination - `ToBase64` and `FromBase64` — encode or decode UTF-8 text. Invalid base64 is a syntax error. - `JsonEscape` — escape the inside of a JSON string literal without adding surrounding quotes. - `XmlEscape` — escape XML-sensitive characters such as `<`, `>`, and `&`. - `HtmlEscape` — HTML-encode the value. - `UriEscape` — percent-encode unsafe characters while keeping URI structure such as `:/?&=` intact. - `UriDataEscape` — encode a value for one URI component or query parameter, including structural characters. ### Produce True or False for conditions Comparison filters use ordinal, case-sensitive matching and return the strings `True` or `False`. - `StartsWith "text"` — whether the value begins with the argument. - `EndsWith "text"` — whether the value ends with the argument. - `Contains "text"` — whether the value contains the argument. - `Match "pattern"` — whether the value matches the supplied regular expression. ### Common configuration transformations ``` # VAR.SiteName = " Production API " #{VAR.SiteName | Trim | ToLower} # production api # Built-in deployment context #{CONTEXT.EnvironmentName | ToLower} # production # VAR.Host = "api 01" #{VAR.Host | UriDataEscape} # api%2001 # VAR.Version = "v12.4.0" #{VAR.Version | Replace "^v" "" | StartsWith "12"} # True # VAR.SettingsJson contains quotes and backslashes "settings": "#{VAR.SettingsJson | JsonEscape}" ``` ### Conditional blocks Conditionals are available in Extended mode only. A condition without a comparison is truthy unless it is undefined, empty, whitespace, `False` (case-insensitive), or `0`. Missing variables inside a condition are deliberately false and are not reported as unresolved. ```text #{if VAR.EnableSsl} https://#{VAR.Host} #{else} http://#{VAR.Host} #{/if} ``` Use `unless` to invert the test: ```text #{unless VAR.SkipMigrations} run-migrations=true #{/unless} ``` Conditions can compare two variables or compare a variable with a double-quoted literal using `==` or `!=`. Equality is ordinal and case-sensitive. An operand may also use filters: ```text #{if CONTEXT.EnvironmentName == "Production"}prod=true#{/if} #{if VAR.Region != VAR.DefaultRegion}crossRegion=true#{/if} #{if CONTEXT.EnvironmentName | Contains "Prod"}protected=true#{/if} ``` Blocks may span lines and nest. Each `if` must close with `#{/if}` and each `unless` with `#{/unless}`. `#{else}` is optional and may appear once per block. Iteration syntax such as `#{each ...}` is not supported and is rejected explicitly. ### Literal token-like text and escaping Write an extra leading hash when text must remain literal: ```text ##{VAR.Name} → #{VAR.Name} ##{VAR.Name | ToUpper} → #{VAR.Name | ToUpper} ##{if VAR.FeatureEnabled} → #{if VAR.FeatureEnabled} ``` This matters for templates containing token-like text intended for another templating system. Escaped tokens are not treated as unresolved. ### Unresolved-token modes - **Silent**: Leave the original token in the output and say nothing. This is the default for Legacy when no explicit policy exists. - **Warn**: Leave the token in place and add a deployment warning naming the token and where it appeared. This is the default for Extended when no explicit policy exists. - **Strict**: Fail before the step runs or before transformed package files are written. Use only after warnings have been cleared. Warnings are deduplicated so the same unresolved value does not flood the deployment log across every step and machine. Tokens in a branch that actually renders are checked; tokens in skipped branches are not. A missing name used only as an `if` or `unless` condition is expected and is not warned about. An unresolved filtered token such as `#{VAR.Missing | ToUpper}` remains intact and follows the selected policy. A resolved token is transformed only after its complete nested value has been resolved. ### Where substitution runs The server resolves string values in the deployment step context before dispatch, including scripts and bound step properties. Package-deployment helpers can also replace variables inside files on the agent: - `Convert-JawsVariableReplace` - `Convert-JawsJsonHierarchicalVariableReplace` Extended mode is carried to the agent so package files use the same parser and unresolved-token policy as server-side values. Under Strict, all matching files are transformed in memory and checked before any file is written, avoiding a half-transformed package. > **// Agent compatibility - Update agents before Extended package-file replacement.** > > An older agent can ignore the new mode fields and would apply Legacy replacement inside package files. Jaws therefore blocks an Extended step that can call either file-replacement helper when the target agent does not advertise variable-filter support. Detection also covers calls made through imported project script modules. Server-side substitution and Legacy workspaces continue normally. ### Authoring mistakes fail loudly in Extended mode - Unknown filters and missing or extra arguments. - Invalid numeric arguments, out-of-range substrings, invalid base64, or invalid regular expressions. - Missing conditions, unclosed blocks, mismatched closing tags, stray `else`, or multiple `else` branches. - Unsupported iteration syntax. - An unclosed quoted filter argument prevents the text from being recognized as a token; it remains literal, so inspect token-like text when auditing warnings. ### Move an existing workspace safely - Keep substitution syntax on Legacy and set unresolved tokens to Warn. - Run representative deployments for every environment and target role. - Define or correct genuine missing variables; escape intentional token-like text with `##{...}`. - Update agents used by steps or script modules that replace variables inside package files. - Switch syntax to Extended and verify rendered scripts, properties, and package configuration. - Optionally change unresolved tokens to Strict after the warning set remains empty. > **Related - Keep reading** > > - [Variables & Secrets](https://www.jawsdeploy.net/features/variables) — scopes, secrets, nesting, and output variables. > - [Variable Resolution Rules](https://www.jawsdeploy.net/guides/variable-resolution-rules) — which value wins when scopes overlap. > - [Nested and Referenced Variables](https://www.jawsdeploy.net/guides/nested-and-referenced-variables) — composing values from other variables. > - [Create a workspace](https://www.jawsdeploy.net/rest-api/workspaces-create) — select `variableSyntaxMode` through the REST API. ## Output Variables Source: https://www.jawsdeploy.net/guides/output-variables | Section: Variables & Configuration A script step can record values that later steps read back by reference — no temp files, no database, no shared state. Output variables let a deployment step record a value that later steps in the same deployment read back by name. The value is stored in the step's execution context and injected into subsequent steps automatically — no temp files, no shared database, no environment-variable workarounds. ### Two scopes control visibility - **Machine** (default) — the value is tied to the target machine that ran the step. Later steps on the same target read it via `ThisMachine`; steps on other targets can reference it by machine name. - **Global** — the value is shared across the whole deployment regardless of which machine produced it. Use this for values every subsequent step needs: a generated URL, a resource ID, a computed version number. > **// Lifecycle - Output values do not persist across deployments.** > > They are available to every step that runs after the step that produced them in the same deployment run, and discarded when the deployment completes. ### Emit from PowerShell (PS7+) Use `Set-JawsOutputValue` in any PowerShell (PS7+) script step. `-Scope` defaults to `Machine`; pass `Global` to share the value across the whole deployment. `Set-JawsOutputValueGlobalScope` is a shorthand for the global case. ``` # Machine scope (default) — visible to later steps running on the same target Set-JawsOutputValue -Name "ServicePort" -Value $port # Global scope — visible to all later steps in the deployment Set-JawsOutputValue -Name "DeployedUrl" -Value $url -Scope Global # Shorthand for Global scope Set-JawsOutputValueGlobalScope -Name "BuildVersion" -Value $version # Emit a number or a secret (secrets are redacted in logs immediately) Set-JawsOutputValue -Name "InstanceCount" -Value 3 -Type Number -Scope Global Set-JawsOutputValue -Name "ApiToken" -Value $token -Type Secret -Scope Global ``` ### Emit from PowerShell 5.1 PS5 steps use the same `Set-JawsOutputValue` and `Set-JawsOutputValueGlobalScope` functions. The difference from PS7+: `-Type` and `-Scope` are plain strings rather than typed enum values. ``` # Machine scope (default) Set-JawsOutputValue -Name "ServicePort" -Value $port # Global scope — pass scope as a string Set-JawsOutputValue -Name "DeployedUrl" -Value $url -Scope "Global" # Shorthand Set-JawsOutputValueGlobalScope -Name "BuildVersion" -Value $version # Typed values — type is also a string in PS5 Set-JawsOutputValue -Name "InstanceCount" -Value 3 -Type "Number" -Scope "Global" Set-JawsOutputValue -Name "ApiToken" -Value $token -Type "Secret" -Scope "Global" ``` ### Emit from Python Python steps use `jaws.set_output_value()` from the built-in `jaws` module. The `scope` and `type` parameters are strings and default to `"Machine"` and `"Text"` respectively. ``` import jaws # Machine scope (default) jaws.set_output_value("ServicePort", port) # Global scope jaws.set_output_value("DeployedUrl", url, scope="Global") # Shorthand for Global scope jaws.set_output_value_global_scope("BuildVersion", version) # Typed values jaws.set_output_value("InstanceCount", 3, type="Number", scope="Global") jaws.set_output_value("ApiToken", token, type="Secret", scope="Global") ``` ### Consume in a later step Output variables from earlier steps are injected into each subsequent step's parameter context automatically. Reference them with `#{...}` syntax in script code, step property fields, or variable values. ### Choose the pattern for your scope - `#{OUTPUT.Step..Global.}` — value emitted with Global scope - `#{OUTPUT.Step..ThisMachine.}` — Machine scope, step runs on the same target - `#{OUTPUT.Step..MachineOutput..}` — Machine scope, referencing output from a specific named target > **// Step names - Use the display name exactly as it appears in the deployment process — spaces and all.** > > For example, if your step is named `Deploy Web App`, the reference is `#{OUTPUT.Step.Deploy Web App.Global.DeployedUrl}`. ``` # PowerShell — read directly from the parameters dictionary $url = $Jaws.Parameters["OUTPUT.Step.Deploy Web App.Global.DeployedUrl"].Value # In a step property field or variable value, use #{...} substitution: # #{OUTPUT.Step.Get Config.Global.ConnectionString} # Python — read from jaws.parameters url = jaws.parameters.get("OUTPUT.Step.Deploy Web App.Global.DeployedUrl") # Machine-scoped value from the same target port = jaws.parameters.get("OUTPUT.Step.Configure Service.ThisMachine.ServicePort") # Machine-scoped value from a specific named target port = jaws.parameters.get("OUTPUT.Step.Configure Service.MachineOutput.web-01.ServicePort") ``` ### Supported types Set `Secret` whenever the value is sensitive — it is redacted as `****` in all deployment logs immediately after emission. - **Text** — plain string (default) - **Number** — numeric value, resolved as a number in step parameters - **Secret** — string value, redacted as `****` in all deployment logs immediately - **Boolean** — `true` or `false` - **Date** — date/time value - **Json** — raw JSON string, passed as-is to later steps ## Variable Replacement in Files Source: https://www.jawsdeploy.net/guides/variable-replacement-in-files | Section: Variables & Configuration Push resolved variable values into your config files at deploy time - with token placeholders, JSON hierarchy matching, and globbed file lists. Defining a variable in Jaws Deploy is only half the job. A variable like `ConnectionStrings:Default` or `Cache.Endpoint` lives in the Jaws database, scoped to a workspace, project, environment, tag, or target. At deploy time you usually need that value to land *inside a file* on the target machine - an `appsettings.json`, a `web.config`, an `.env`, a YAML manifest, a plain text template. This guide explains the three mechanisms Jaws Deploy uses to push variable values into files, and how to configure the file lists they run against. All three are configured on the **Deploy package** step template, under its **Transformations** property group. If you assemble your own step templates, the same building blocks are available as the `Convert-JawsVariableReplace`, `Convert-JawsJsonHierarchicalVariableReplace`, and `Convert-JawsXmlTransform` / `Convert-JawsJsonTransform` functions from the built-in `JawsCommon` module. > **// Mental model - Variables live in Jaws. Replacement writes them into your files.** > > Your build artifact ships with placeholders or with default values. During deployment, Jaws resolves each variable for the current environment and target (see [Variable Resolution Rules](https://www.jawsdeploy.net/guides/variable-resolution-rules)), then rewrites the files you nominate so the deployed copy carries real, environment-specific values - without rebuilding the package. ### The three mechanisms ### Where you configure this Open the **Deploy package** step in your deployment process and expand the **Transformations** group. You will find three multi-line fields, each taking a newline-separated list of files: - **Config transforms** - `[json|xml]: source => target` transform definitions. - **Replace variables in files** - files to run token-based `#{...}` replacement against. - **Replace variables in JSON files located based on hierarchical variable names** - JSON files to run hierarchy-based replacement against. All paths are **relative to the package root** (the extracted contents of your package), and all three fields support **globbing**. The step runs them in a fixed order with optional custom-script hooks in between - see [Processing order](#processing-order) below. ### Listing and globbing files Each Transformations field is a list, **one entry per line**. Blank lines are ignored and surrounding whitespace is trimmed, so you can format the list for readability. Paths are resolved against the package root, then expanded as **globs** before any replacement happens. Globbing means you do not have to enumerate every file: - `*` matches any run of characters within a single path segment. - `**` matches across directory boundaries (any depth). - A literal path with no wildcards matches exactly one file. If a glob matches several files, the replacement runs against every match. If it matches nothing, that line is simply a no-op - which is also the most common reason a replacement "silently does nothing": the path is wrong relative to the package root, or the build did not include the file. ### Examples of file list entries Each line is relative to the package root. Use backslashes (Windows agents) consistently. ``` # exact file appsettings.json # every appsettings.{env}.json in the root appsettings.*.json # every web.config at any depth **\web.config # every .json under config\, any depth config\**\*.json # a single nested file services\api\appsettings.Production.json ``` ### Token-based replacement List a file under **Replace variables in files** and Jaws scans its text for `#{...}` tokens, replacing each one with the resolved variable value. It works on **any text file** - JSON, XML, YAML, `.env`, `.conf`, `.ps1`, an HTML template - because it is a pure text substitution. A few rules worth knowing: - **Prefixed and bare names both work.** User variables are stored internally with a `VAR.` prefix. A variable you named `ApiBaseUrl` can be referenced as either `#{ApiBaseUrl}` or `#{VAR.ApiBaseUrl}`. Use the bare form unless you need to disambiguate from a system parameter. - **Names are restricted to a token charset.** A token name may contain letters, digits, and the characters `. - _ :` and spaces. Anything else ends the token, so `#{My-Var.v2}` is valid but `#{a+b}` is not treated as a token. - **Nested / recursive resolution.** If a variable value itself contains `#{...}`, it is resolved too. `installPath = C:\#{appName}\#{version}` expands fully. Circular references are detected and fail the deployment rather than looping. - **Unknown tokens are left untouched.** If no variable matches, the literal `#{...}` text stays in the file - a useful signal that something is misspelled or out of scope. ### Before and after File listed under "Replace variables in files". Variables resolved for the Production environment. ``` // appsettings.json (in the package, with tokens) { "ApiBaseUrl": "#{ApiBaseUrl}", "ConnectionStrings": { "Default": "Server=#{DbHost};Database=#{DbName};" }, "FeatureFlags": { "NewCheckout": "#{Features.NewCheckout}" } } // after deployment to Production { "ApiBaseUrl": "https://api.example.com", "ConnectionStrings": { "Default": "Server=prod-sql-01;Database=shop;" }, "FeatureFlags": { "NewCheckout": "true" } } ``` > **// Escaping - Need a literal #{...} in the file?** > > Double the leading hash. `##{thing}` is the escape sequence: Jaws emits a literal `#{thing}` and skips substitution. This matters when your file legitimately contains `#{...}` syntax meant for another tool (some templating engines, shell here-docs, etc.). ### Hierarchy-based replacement (JSON) List a JSON file under **Replace variables in JSON files located based on hierarchical variable names** and Jaws takes a different approach: instead of looking for placeholders, it **locates existing nodes by name**. The variable name maps to a JSON path, where each colon (`:`) is one level of nesting. To set the value of `bar` in `{ "foo": { "bar": 123 } }`, create a variable named `foo:bar` with the new value. Jaws walks the document, finds the node at `foo` -> `bar`, and replaces it. No edit to the file's source is needed - the JSON you built ships unmodified and Jaws rewrites the matching nodes at deploy time. More rules: - **Only user variables participate.** Hierarchy replacement considers variables with the internal `VAR.` prefix - i.e. the variables you defined. System parameters are ignored. - **Missing nodes are skipped.** If the JSON has no node at the variable's path, nothing happens for that variable. Hierarchy replacement never *adds* keys; it only updates ones that already exist. - **Scalars replace scalars.** A string, number, or boolean value replaces the target value in place, preserving JSON type where possible. - **You can inject whole objects/arrays.** If the target node is an array or object and your variable's value is a string of valid JSON, Jaws parses it and substitutes the parsed structure. If the value is not valid JSON in that case, the deployment fails with a clear error. ### Variable names map to JSON paths File listed under the hierarchical-replacement field. The JSON ships with real defaults - no tokens. ``` // appsettings.json (shipped as-is, no placeholders) { "Logging": { "LogLevel": { "Default": "Information" } }, "AllowedHosts": "*", "Cors": { "Origins": ["http://localhost:3000"] } } // Jaws variables defined for Production: // Logging:LogLevel:Default = "Warning" // AllowedHosts = "app.example.com" // Cors:Origins = ["https://app.example.com"] (value is valid JSON) // after deployment to Production { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "app.example.com", "Cors": { "Origins": ["https://app.example.com"] } } ``` > **// Token vs hierarchy - Which one should I use?** > > Use **token-based** when you control the file and are happy to drop `#{...}` placeholders into it - it is the most flexible and works on any text format. Use **hierarchy-based** when you cannot or do not want to template the file (for example a stock `appsettings.json` you would rather ship unedited), and the values you need to change already exist as JSON nodes. They are not exclusive: you can list the same JSON file in both fields and let token replacement run first, then hierarchy replacement tidy up the structured nodes. ### Processing order The Deploy package step always runs the transformations in the same order, so you can layer them predictably. Between each stage there is a custom-script hook you can use for anything the built-in steps do not cover: 1. **Extract package** to the work folder. 2. *Hook: After package is extracted.* 3. **Config transforms** (XDT / JSON merge) - restructure files first. 4. *Hook: After config transform is applied.* 5. **Token replacement** - `#{...}` substitution across the listed files. 6. **JSON hierarchy replacement** - locate-and-replace by variable name. 7. *Hook: After variables are replaced in files.* 8. **Copy to installation directory** (optionally purging it first). 9. *Hook: After package is deployed.* Because token replacement runs before hierarchy replacement, a `#{...}` token can supply the value that a later hierarchy pass reads - but not the other way round. Keep that ordering in mind when a file is processed by more than one mechanism. ### Common mistakes - **Wrong base path.** All file lists are relative to the *package root*, not the agent working directory or the install directory. Match the folder layout inside the package. - **Glob matched nothing.** A typo or a file the build excluded means the line is a silent no-op. Check the deployment log - each processed file is logged by name. - **Expecting hierarchy replacement to add keys.** It only updates nodes that already exist. To introduce a brand-new section, use a config (JSON merge) transform instead. - **Secret values in plain files.** Replacement writes the resolved value to disk in clear text. That is expected for config, but do not point replacement at files that get committed or shipped back to an artifact store. - **Forgetting the escape.** A genuine `#{...}` that belongs to another tool will be eaten by token replacement unless you write it as `##{...}`. > **Next - Related reading** > > - [The Power of Variables](https://www.jawsdeploy.net/guides/the-power-of-variables) - why variables sit at the centre of the Jaws model. > - [Variable Resolution Rules](https://www.jawsdeploy.net/guides/variable-resolution-rules) - how a value is chosen when several scopes match. > - [Nested and Referenced Variables](https://www.jawsdeploy.net/guides/nested-and-referenced-variables) - composing variables from other variables. ## Creating Releases Source: https://www.jawsdeploy.net/guides/creating-releases | Section: Releases & Lifecycles A release is an immutable snapshot of packages, variables, and deployment process for a specific version. A release is the unit of promotion in Jaws Deploy. It carries three frozen pieces under a single version number: the packages, the variable definitions, and the deployment process. Once created, none of these change. ### What a release captures - **Packages**: Exact package versions at release time - zip, tar, NuGet, whatever the steps consume. - **Variable schema**: Variable definitions at release time. Values still resolve per environment - the *set* is what's frozen. - **Deployment process**: The ordered steps the project had when the release was created. ### When to create one The usual moment is after a successful CI build. CI calls the Jaws Deploy API or SDK and passes: the project, the version, and any package mappings. The platform snapshots the rest from the project's current state. ### How CI typically creates a release Same shape works for TeamCity, GitHub Actions, GitLab, Jenkins, and Azure Pipelines. ``` Connect-JawsDeploy ` -Url "https://app.jawsdeploy.net" ` -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "2026.5.17.${env:BUILD_NUMBER}" ` -Packages @{ "Checkout.Web" = "2026.5.17" } ` -ReleaseNotes (Get-Content .\CHANGELOG.md -Raw) ``` > **// Versioning - SemVer 2.0 is the recommended scheme.** > > Jaws Deploy doesn't enforce a versioning scheme, but stays out of the way for SemVer 2.0 (`1.4.2`, `2.0.0-rc.1`, `2026.5.17+build.4218`). The convention that earns its keep: CI generates the version from the build, humans never type one. ### What you don't snapshot A release does not capture environment state, target state, or live external systems. Those drift independently of releases. If the database schema in Staging is different from Production, re-deploying the same release to both will hit different state - that's a Staging/Prod parity problem, not a release problem. ## Executing Deployments Source: https://www.jawsdeploy.net/guides/executing-deployments | Section: Releases & Lifecycles Deployment is the act of running a release against an environment. Same release, different scope. Deployment is the verb form of release. The release is the fixed plan; deployment is one execution of that plan against a specific environment. The same release can be deployed many times - useful for re-runs, rollbacks via re-deploy of the previous release, or re-applying to a fresh target. ### What happens when you click deploy The platform runs through a fixed sequence: ### What the platform does, in order - **Resolve targets** - which targets in the environment match each step's scope? - **Resolve variables** - pre-compute the values each step will see. - **Run each step in order** - fanning out across matching targets, gating on the slowest. - **Stream logs** - per step, per target, live to the UI. - **Record the outcome** - the run status, the error and warning counts, and timing per step. > **// Outcome - A finished deployment is not the same as a successful one.** > > Status answers one question: did the engine complete the run? A script that exits non-zero does not fail its step and does not fail the deployment - the failure is recorded as an error and the run carries on. Failed and Cancelled are reserved for the engine itself being unable to finish. Judge a deployment on three things together: status Completed, an error count of zero, and a log showing it reached the targets you expected. ### Reading the outcome Two things describe a finished deployment, and they answer different questions. - **Status** - did the engine complete the run? `Completed` means it reached the end of the step list. `Failed` and `Cancelled` mean it could not get there: an agent it could not reach, a package or script module it could not deliver, a step that timed out, a deployment that outlived its maximum duration, or a cancellation. - **Error count** - did the work report problems? A script that exits with a non-zero code, or a PowerShell command that throws, is recorded as an error against its step. The step is still reported as completed, and the deployment still runs to the end. So `Completed` on its own is not success. A deployment did what you asked when its status is `Completed`, its error count is `0`, and it reached the targets you expected. ### Error action, and what it actually controls Each step has an error action of **Stop** or **Continue**. It chooses the level a script failure is logged at, and nothing more. - **Stop** logs the failure as an error, so it counts towards the deployment error count. - **Continue** logs it as a warning, so it does not. Neither setting ends the deployment. On a project that leans on Continue, an error count of zero stops being proof of a clean run, and the warnings need reading too. ### Later steps still run A step that recorded errors does not stand down the steps after it. Every remaining step is still offered, and only the execute condition of that step can hold it back. - **Always** ignores what came before. - **All previous steps succeeded** is the one condition that reacts to earlier failures. It skips the step when the steps before it recorded any errors between them - which is why only earlier steps set to Stop can trigger it. - **Variable check** looks at a boolean variable instead. A step held back this way is recorded as skipped, not failed, and adds nothing to the error count. ### A step with no targets is not a failure If the machine or tag filters of a step match no agent - or the only agents they match are disabled - the step has nothing to run on. That is not treated as an error. The step is recorded as finished, contributes no errors, and the deployment can reach `Completed` having done nothing at all. The log is what tells the two cases apart: a step that ran shows one child node per target, and a step that matched nothing shows none. When a deployment has to prove it landed somewhere, check the targets in the log rather than the status alone. ### Re-deploying The same release can be deployed to the same environment more than once. Common reasons: the previous run failed and you've fixed the cause, you want to re-apply a configuration drift, or you're refreshing a freshly-rebuilt target. Each deployment gets its own record - history shows N runs, not one. ## Release Progression with Lifecycles Source: https://www.jawsdeploy.net/guides/release-progression-with-lifecycles | Section: Releases & Lifecycles A lifecycle is a small state machine over environments. Dev -> Staging -> Production is just one possible shape. A lifecycle is an ordered set of phases. Each phase contains one or more environments. A release in a project pinned to a lifecycle can only be deployed to a phase once all previous phases have succeeded - unless a phase is marked optional, or a channel overrides the path. ### What a lifecycle looks like The default lifecycle has three phases: Dev, Staging, Production. Each phase has a single environment. A release enters Dev, then becomes eligible for Staging once Dev succeeds, then becomes eligible for Production once Staging succeeds. ### Beyond the default Lifecycles can encode more than the canonical three-stage path. **Multiple environments in one phase** A `Pre-prod` phase containing both `Staging` and `UAT`. The release must succeed in *both* before Production becomes eligible. **Skippable when needed** A `Smoke` phase that's optional. Standard releases use it. Hotfix releases skip it - recorded as skipped, not as a gap in history. ### Channels A channel attaches a lifecycle to a project. The default channel uses the standard lifecycle. A hotfix channel can attach a shorter lifecycle that skips Dev. Releases pick which channel they're created under - same project, two paths. > **// Approvals on phase boundaries - A phase can require manual approval before a deployment runs.** > > Useful for the Staging -> Production boundary. The platform records who approved, when, and which release. The deployment doesn't start until approval is granted. ## Deployment History and Auditing Source: https://www.jawsdeploy.net/guides/deployment-history-and-auditing | Section: Releases & Lifecycles Every deployment writes a record - what release, what environment, who triggered it, how long it took, what the steps did. Jaws Deploy records every deployment. The record covers what (release version), where (environment, targets), when (start/end timestamps, step timings), who (the user or service account that triggered it), and how (the live log of each step's output). ### What's in the record - **Release**: The release version that was deployed. Linked to the release page with package versions and variable schema. - **Environment**: The environment the deployment ran against. Resolved targets and scoped variables for that environment. - **Timing**: Start, end, total duration. Per-step duration. Useful for spotting performance regressions. - **Initiator**: The user or service account that triggered the deployment, plus the approver if approvals were required. - **Step outcomes**: Per-step status (pass/fail/skipped), per-target output, full log captured live. - **Failures**: The failing step, the failing target, the error message - all surfaced before drilling into the log. ### What this is good for Incident response is the most common use. "What changed between Tuesday's deploy and Friday's?" becomes a diff of releases. "Did this release reach Production?" is one query. "Who approved last month's Production deploys?" is a filter. The second use is audit. Compliance frameworks that require evidence of segregation-of-duty, approval trails, or change records draw on this data directly. > **// Retention - History is kept indefinitely unless you choose to expire it.** > > Cloud workspaces keep deployment records for the lifetime of the workspace. Stack installations control retention through database backup/archive policies - the data lives in your database. ## Channel Version Rules Source: https://www.jawsdeploy.net/guides/channel-version-rules | Section: Releases & Lifecycles Let one project ship a stable stream and a beta stream side by side. A channel gates which release versions may be created in it, and which package versions may be pinned into those releases. A **channel** is a named lane inside a project. It carries its own lifecycle, so a release created in the *Beta* channel can promote through a different set of environments than one created in *Stable* - same project, same deployment process, different route to production. **Channel version rules** decide what is allowed into the lane. A channel can filter on the **release version** itself, and on the **package versions** pinned into that release - so `3.0.0-beta7` never lands in *Stable*, and a nightly build of a package never reaches a release destined for production. > **// The shape - Two streams, one project** > > Most teams that maintain a released product also maintain a preview of it. Both are built from the same repository, both use the same deployment process, and both need to reach different environments. Two projects would duplicate everything and split the version history in half. Two channels in one project is the shape that fits: a shared deployment process, a lifecycle each, and version rules that keep the streams from leaking into one another. ### What a channel can gate - **The release version**: A version range and a prerelease setting. A release can only be created in the channel when its own version satisfies both. Leave them alone for no constraint. - **Package versions**: An ordered list of rules, each matching packages by ID and constraining which of their versions may be pinned. Packages matched by no rule stay unconstrained. - **The same answer everywhere**: The editor, the release form, and the API all evaluate rules through one matcher, so the version the UI offers you is exactly the version the server will accept. ### The release version rule Each channel has one release version rule, made of two independent parts, plus a helper for auto-suggested versions. Everything is optional. - **Version range** - bounds the version **number**, written in interval notation. A square bracket includes the endpoint, a round bracket excludes it, and a missing endpoint means unbounded. - **Prerelease versions** - a dropdown deciding whether prereleases are allowed at all, and which ones. - **Tag to add to auto-suggested versions** - the tag Jaws appends to the version it proposes for you, e.g. `beta`. The one piece of range notation worth committing to memory is the first line below: a bare version is a **minimum**, not an exact match. Writing `1.0.0` when you meant "only 1.0.0" opens the channel to every version above it instead. ### How to write the range you mean The same notation applies to the release version rule and to package rules. ``` 1.0.0 1.0.0 and anything above it - a bare version is a MINIMUM, not an exact match. This is the one that surprises people. [1.0.0] exactly 1.0.0, and nothing else [2.0,3.0) 2.0 or above, but below 3.0 - the usual way to say "2.x only" (2.0,3.0) above 2.0 and below 3.0 - both endpoints excluded [2.0,) 2.0 or above, with no upper bound (,3.0] 3.0 or below, with no lower bound ``` #### Choosing whether prereleases are allowed The range says nothing about prereleases - that is the **Prerelease versions** dropdown's job. It offers four choices: - **Any version** - no constraint. Stable and prerelease versions are equally welcome. - **Stable only - no prereleases** - the channel accepts `2.4.0` and rejects `2.4.0-beta1`. This is what a production channel usually wants. - **Prereleases only** - the mirror image: `2.4.0-beta1` is accepted and `2.4.0` is rejected. Useful for a channel that must never be handed a final build. - **Matching a pattern...** - your own regular expression over the tag, for when the presets are too blunt. `^beta.*$` accepts `beta`, `beta1` and `beta.3` but not `rc1`; `^(beta|rc).*$` opens the lane to both. A custom pattern is matched against the prerelease label **without** the leading `-`, and against an **empty string** for a stable version - which is why the stable-only preset is simply `^$`. The dropdown is not a stored field of its own: it is derived from the pattern. A channel created over the REST API with `versionTagRegex` set to `^$` shows up in the editor as *Stable only*, and anything the presets do not recognise shows as *Matching a pattern...* with your expression intact. There is one rule, and two ways to write it. > **// Read this twice - The range bounds the number, not the tag** > > The two parts of the rule are genuinely independent, and the range's endpoints compare on the version **number** alone - the prerelease tag takes no part in that comparison. For `[2.0,4.0)`: If you have met NuGet version ranges elsewhere, note that this is deliberately **not** how a raw NuGet range behaves. NuGet sorts a prerelease below its own release, so a raw `[2.0,4.0)` would reject `2.0.0-beta` while happily accepting `4.0.0-beta`. For a dependency resolver that is defensible; for a channel gate it is not. A channel configured as "2.0 to 4.0, betas only" has to accept `2.0.0-beta`, so Jaws compares the bounds on the number and leaves the prerelease question entirely to the dropdown. #### Why a tag for auto-suggested versions exists When you open the release form, Jaws suggests the next version for you. With a channel selected the suggestion is seeded from the releases **already in that channel**, which is what lets a stable and a prerelease stream keep independent numbering, and is then nudged to satisfy the channel's rule: it snaps up to the minimum of the version range, and appends the tag you configured. That last part needs its own field because a suggested version has no prerelease tag on its own - so a channel that requires one would reject its own suggestion. Set the tag to `beta` and the form suggests `1.4.0-beta` instead of `1.4.0`. Leave it blank to suggest plain version numbers. The field is hidden for a stable-only channel, where a prerelease tag would be a contradiction, and the editor rejects a tag that the channel's own pattern would not match. ### Package version rules A channel also carries an ordered list of package rules. Each rule has: - a **package filter** - a glob over the package ID, e.g. `*` or `MyApp.*`. Case insensitive; `*` and `?` are the wildcards and every other character is literal, so a `.` in a filter is a dot, not a wildcard. - a **version range** and a **prerelease versions** setting, with exactly the same meaning - and the same dropdown - as on the release version rule. Rules are evaluated **in order, and the first rule whose filter matches a package wins**. A package matched by no rule is unconstrained. That ordering is the whole design: put the specific filters at the top and the catch-all at the bottom. So a *Stable* channel that wants stable packages everywhere except for one component that is allowed to ship release candidates reads: ### Specific first, catch-all last The first matching filter wins, so `MyApp.Frontend` never reaches the second rule. ``` 1. MyApp.Frontend range: (blank) prereleases: matching ^(rc|$) 2. * range: (blank) prereleases: stable only ``` Each filter may only be used once per channel - two rules with the same filter would mean the second could never match, so the editor rejects it rather than letting you save a rule that does nothing. A rule that constrains a package which has **no** compliant version available is not silently ignored either. See below. ### What you see when creating a release The rules are applied while you fill the form in, not only when you submit: - **The version box** is pre-filled with a suggestion that already satisfies the channel's rule. - **Package dropdowns are filtered.** For each package you only see the versions the selected channel allows, newest first. - **Switching channel re-pins packages.** If a version you had selected is not allowed in the channel you just picked, Jaws moves it to the newest allowed version and tells you which packages it changed. - **A package with nothing to pin blocks the release.** If no available version of a package satisfies the rule, you get an explicit error naming the package, and the create button is disabled until you push a compliant version or choose a different channel. - **Anything the server rejects appears in a summary** at the bottom of the form - for example *"Release version 2.4.0 is not allowed in channel 'Beta': prerelease tag '(none)' does not match ^beta.*$"*. ### Behaviour worth knowing about - **A channel with no rules is unrestricted.** Every rule field is optional and starts blank, so a channel constrains only what you explicitly tell it to. The channels page lists such a channel as *No restrictions*. - **The server is the authority.** The release API loads the channel and its rules from the database rather than trusting anything on the request, so an API caller cannot pick which rules apply to it. - **Rules gate creation, not deployment.** A release that already exists keeps working if you later tighten the channel's rules. The rule is a gate on the way in. - **CI gets the same errors.** Create a release over the REST API with a non-compliant version or package and the response tells you which channel rejected it and why - the same sentence the UI shows. - **Patterns are bounded.** Tag patterns are compiled non-backtracking with a timeout, so a pathological expression cannot hang anything. Backreferences and lookarounds are rejected at save time as a consequence. ### Setting the rules up Open a project, go to **Channels**, and add or edit a channel. The release version rule sits directly under the lifecycle picker, with a **try a version against this rule** box next to it - type a version and it tells you *Allowed* or *Rejected*, with the reason, before you save anything. Package rules are the table below it; **Add package rule** appends a row, and rows are matched top to bottom. Over the REST API the same rule fields hang off the channel endpoints. On [update](https://www.jawsdeploy.net/rest-api/project-channel-update) an **omitted** field leaves the stored value alone while an **empty** one clears it, and that applies to the package rules as a whole: omit `packageRules` to leave them untouched, or send an empty list to remove all of them. ### Create a beta channel with package rules Rules are matched in the order they appear in packageRules. ``` POST /api/project/channel Authorization: Basic Content-Type: application/json { "projectId": "prj_abc123", "name": "Beta", "lifecycleId": "lc_preview", "versionTagRegex": "^beta.*$", "versionDefaultTag": "beta", "packageRules": [ { "packageFilter": "MyApp.Frontend", "versionRange": "[2.0,3.0)" }, { "packageFilter": "*", "versionTagRegex": "^$" } ] } ``` > **Related - Keep reading** > > - [Creating Releases](https://www.jawsdeploy.net/guides/creating-releases) - what a release captures and how CI drives creation. > - [Release Progression with Lifecycles](https://www.jawsdeploy.net/guides/release-progression-with-lifecycles) - the environments a channel's lifecycle walks a release through. > - [List](https://www.jawsdeploy.net/rest-api/project-channels-list), [create](https://www.jawsdeploy.net/rest-api/project-channel-create) and [update](https://www.jawsdeploy.net/rest-api/project-channel-update) channels, and [create a release](https://www.jawsdeploy.net/rest-api/releases-create), in the REST API. ## Using Package Feeds Source: https://www.jawsdeploy.net/guides/using-package-feeds | Section: Packages A feed is the catalogue. A package is a versioned artefact in it. A deployment step references both. Three nouns: **feed**, **package**, **package version**. A feed is the catalogue (the artefact server). A package is a named artefact (`Checkout.Web`). A package version is a specific build of that package (`Checkout.Web 2026.5.17`). ### The flow CI publishes a package version to a feed. The Jaws Deploy project has a step that references the package by name. When a release is created, the release picks a specific version of that package - either the latest, or one provided by CI. The release is now locked to that version forever. **When to use it** When you don't already have an artefact server. Push packages directly to Jaws Deploy. One less moving part. **When to use it** When CI already publishes to NuGet, Artifactory, Azure Artifacts, S3, or similar. Register it as an external feed and reference packages by name and version. ### What package formats are supported Zip archives, tarballs, and NuGet packages all work as deployment packages. The deployment step extracts the package on the target before running post-deploy hooks. Container images are a different shape - they're typically deployed by a step that calls a container orchestrator rather than by extracting onto a target. > **// Immutability - A given package version is pushed once.** > > You cannot push a different artefact under the same version number. That sounds like a constraint until the first time someone tries to "just rebuild and push the same version" the day after a Production release. ## Built-in Package Store Source: https://www.jawsdeploy.net/guides/built-in-package-store | Section: Packages Useful for teams that do not already have a NuGet, Artifactory, or S3 setup they want to keep. Jaws Deploy ships with a built-in package store. It's a feed that lives inside the workspace - no separate server to operate, no separate credentials, no extra cost. Push packages to it directly from CI. ### Pushing packages Three common ways: the PowerShell SDK, the REST API, or the NuGet protocol. CI tools that already know how to push NuGet packages can target the built-in feed as a NuGet source with no special configuration. ### From a CI build step after producing the package The API key needs `feeds.push` permission on the workspace. ``` Connect-JawsDeploy -Url "https://app.jawsdeploy.net" -ApiKey $env:JAWS_API_KEY Push-JawsDeployPackage ` -Workspace "default" ` -Feed "built-in" ` -Path ".\\out\\Checkout.Web.2026.5.17.zip" ``` > **// When the built-in feed is the right call - Teams shipping fewer than ~50 artefact versions per day rarely need anything else.** > > The built-in feed scales well past that - the question is more about whether you already have an artefact server you'd be operating in parallel for no benefit. If you don't, use the built-in. If you do, connect the existing one as an external feed. ## Connecting External Feeds Source: https://www.jawsdeploy.net/guides/connecting-external-feeds | Section: Packages Most teams already have an artefact store. Connecting it as an external feed keeps that investment in place. An external feed is any artefact server Jaws Deploy can pull from. Configuration is one-time: provide the URL, the credentials, the feed type. From then on, deployment steps can reference packages by name and version, and the platform fetches them at deploy time. ### Common external feed types - **NuGet (any host)**: Public nuget.org, GitHub Packages, MyGet, a self-hosted ProGet. Anything that speaks the NuGet v2/v3 protocol. - **JFrog Artifactory**: Generic, NuGet, npm, or Maven repos. Authenticate with an API key or username/password. - **AWS S3**: Treats the bucket as a flat package store. Versioning is by S3 object version or by version in the object key. - **Azure Artifacts**: Private NuGet, npm, or universal feeds in Azure DevOps. Personal access token or service connection. > **// Credentials - Use an account scoped to read-only access where possible.** > > Jaws Deploy only needs to *consume* packages from external feeds. Pushing is done from CI, against the credentials CI already holds. A read-only credential limits blast radius if the workspace is compromised. ### Mixed feeds A single project can pull from both the built-in feed and one or more external feeds. The release locks each package to a specific feed and version when it's created. Promotion across environments uses the same locked references - no risk of pulling a different artefact from a different feed when the release reaches Production. ## Regional Package Delivery Source: https://www.jawsdeploy.net/guides/regional-package-delivery | Section: Packages Keep package bytes close to your servers - for teams in Australia, the US, or anywhere far from the Jaws datacenter. Two independent levers: a regional package store, and private feeds your agents download from directly. Jaws Deploy runs its web app and database in Germany. For a team whose servers are in Sydney, Perth, or the US west coast, that distance is invisible for UI clicks and deploy orchestration - those are small messages. Where it hurts is **package bytes**: a 1 GB artifact that has to cross an ocean on the way in and again on the way out adds minutes to every deployment. This guide covers the two features that keep those bytes local. They are independent - use either, or both: - A **regional package store** ("package store location") for packages you push to the built-in Jaws feed. - **Direct downloads from your own private feeds** (TeamCity, a NuGet server, etc.), so agents pull straight from a feed that is usually already next to them. > **// The problem - Why a package can cross the ocean twice** > > Classically, a package uploaded to the Jaws feed travels Sydney -> Germany on the way in, is staged on the German web server, then streams Germany -> Sydney to the agent on the way out. With your own feed it is worse: Jaws pulls the package from your Sydney feed to Germany, then streams it back to Sydney. The two features below remove those round trips. ### Two levers, one goal ### Lever 1: a regional package store A workspace has a **package store location**. Leave it at the default and nothing changes - packages are stored and served the classic way. Set it to a region (say *Australia*) and, for packages pushed to the built-in Jaws feed: - **Upload** goes straight to that region's storage. Your CI asks Jaws where to put the bytes and uploads them directly to regional storage; only a small metadata call touches Germany. - **Download** comes straight from that region's storage. The agent asks Jaws where to get the package and receives a short-lived link to the regional blob, then downloads directly - chunked and resumable. The result: for a Jaws-feed workspace pinned to Australia, package bytes stay in Australia. Everything else - your login, the UI, the deployment orchestration - keeps running from Germany, which is fine because those are latency-light, not bandwidth-heavy. > **// Decide once - The location is fixed at creation** > > Packages physically live in the chosen region's storage, so the package store location is picked when the workspace is created and cannot be changed later - moving it would mean copying every stored package across the world. Pick based on where the workspace's deployment targets live, not where you happen to sit. If you genuinely need to move an existing workspace, that is a one-off support operation, not a settings toggle. #### Setting it up At **signup**, the package-store-location picker sets the region of your default *Main* workspace. For later workspaces, choose the location in the **create-workspace** dialog, or pass `regionId` to [Create a workspace](https://www.jawsdeploy.net/rest-api/workspaces-create) over the REST API. The picker always includes a **default (no region)** option, so choosing a region is entirely opt-in and never mandatory. Available locations depend on your installation - list them in the app or via [List package store locations](https://www.jawsdeploy.net/rest-api/workspaces-list-regions). New regions are added as customer demand appears (Australia first, US when needed). ### Create an AU-pinned workspace Pass regionId to pin the package store location. Omit it for the default location. ``` POST /api/workspace Authorization: Basic Content-Type: application/json { "name": "Sydney", "regionId": "au" } ``` ### Lever 2: direct downloads from your own feeds If you deploy packages from your **own** feed - a TeamCity build, a NuGet server, an artifact store - the shortest path is almost always the one that skips Jaws entirely. Your feed is usually closer to your agents than any datacenter: a Perth team with TeamCity and agents in Perth should never send a byte to Sydney, let alone Germany. Turn on **Allow direct downloads** on the feed and, at deploy time, agents download each package **straight from the feed**. Jaws only resolves the download descriptor (a tiny metadata call it already makes to list versions) and hands the agent the feed location, credentials, and file details. The bytes go feed -> agent, often on the same LAN. This is independent of the package store location: **any** workspace benefits, regional or not. A German team with a local TeamCity gets exactly the same win as an Australian one. ### How the toggle behaves - **Default off for existing feeds.** Feeds that already exist keep the classic behaviour on upgrade - staged on the server, downloaded through Jaws - so nothing changes until you opt in. The toggle is pre-selected for **newly created** feeds. - **No fallback when it is on.** A direct-download package is never staged in Germany, so if an agent cannot reach the feed the deploy fails with the real feed error. That is by design: the whole point is that your feed is reachable from your agents. - **Keep your agents current.** An agent from before this release cannot use direct downloads; against a direct-download feed it gets a clear "downloaded by agents directly from its feed" error instead of bytes. Update the fleet's agents before you switch the toggle on. - **Agents cache locally.** Each machine fetches a given package version from your feed once and caches it, so turning this on does not hammer your feed on every deploy. > **// Security - Credentials stay inside your workspace** > > In direct-download mode the agent receives the feed's credentials only over the authenticated, HTTPS negotiate response, only for a feed in its own workspace, and never persists or logs them beyond the download. It is your own feed credential handed to your own machine - the same trust boundary agents already operate in when they receive deployment secrets. Regional Jaws-feed links are short-lived, single-blob, and scoped to the requesting agent's workspace in exactly the same way. ### Which should I use? - **You push to the built-in Jaws feed** and your servers are far from Germany -> set a **regional package store** on the workspace. - **You deploy from your own feed** (TeamCity / NuGet / artifact store) that sits near your agents -> turn on **Allow direct downloads** on that feed. - **Both** -> do both. They compose: Jaws-feed packages stay in-region, own-feed packages never leave your premises, and only login, UI, and orchestration talk to Germany. > **Related - Keep reading** > > - [Using Package Feeds](https://www.jawsdeploy.net/guides/using-package-feeds) - how feeds, packages, and steps fit together. > - [Connecting External Feeds](https://www.jawsdeploy.net/guides/connecting-external-feeds) - registering a private feed. > - [Create a workspace](https://www.jawsdeploy.net/rest-api/workspaces-create) and [List package store locations](https://www.jawsdeploy.net/rest-api/workspaces-list-regions) in the REST API. ## Installation Guide (Jaws Deploy Stack) Source: https://www.jawsdeploy.net/guides/installation-guide | Section: Self-Hosting & Admin Stack is the self-hosted form of Jaws Deploy. Same product, same workflow, your infrastructure. Jaws Deploy Stack is a self-hostable distribution of Jaws Deploy. It runs the same control plane and shares the same workflow as Cloud. It targets teams with private networks, compliance requirements, or a preference for owning the platform. ### Before running the installer - One server: Windows Server 2019+ or a current Ubuntu/Debian/RHEL. - Database: MySQL 8 or Postgres 14+. Same host is fine for small installs; separate host is recommended for production. - Outbound HTTPS for license validation; otherwise no internet access required after install. - A DNS name and a TLS certificate for the Stack URL. ### 1. Run the installer Download the Stack installer for your OS. On Windows it's an MSI; on Linux it's a deb/rpm package. The installer creates a `jawsdeploy` service, lays out the application files under `/opt/jawsdeploy` (Linux) or `C:\Program Files\JawsDeploy` (Windows), and writes a templated config file. ### 2. Configure the database connection Edit `/opt/jawsdeploy/config.yaml` (or the Windows equivalent) and set the database connection string. Start the service. The first start runs schema migrations against the database. ### Just the database, the bind address, and the public URL TLS termination is typically handled by a reverse proxy - see the proxy guide. ``` database: driver: mysql host: db.internal port: 3306 name: jawsdeploy user: jawsdeploy password: "..." server: bind: "0.0.0.0:8080" publicUrl: "https://deploy.acme.internal" storage: packages: "/var/lib/jawsdeploy/packages" ``` ### 3. Bootstrap the first admin The installer prints a one-time bootstrap token on the first start. Visit the public URL, paste the token, create the first admin user. From then on, all access is through normal login. > **// Next steps - After Stack is up, the next two pieces are usually a proxy and SSO.** > > See the proxy guide for putting Stack behind nginx/IIS with TLS, and the OIDC guide for wiring Entra ID, Okta, or Google Workspace as your login provider. ## Installing the Jaws Deploy Agent Source: https://www.jawsdeploy.net/guides/installing-jaws-deploy-agent | Section: Self-Hosting & Admin One agent per machine. Outbound-only connection to the control plane. Works in air-gapped environments with bundled modules. The Jaws Deploy Agent is the small background service that runs on every machine target. It connects outbound to the control plane (Cloud or Stack), receives deployment work, runs it locally, and streams logs back. **MSI installer** Download the MSI from the workspace's **Add target** flow. Run it. The installer prompts for the registration token and the control plane URL. **deb/rpm or shell installer** Same flow, different packaging. `dpkg -i jaws-agent_*.deb` (or `rpm -i`), then run `jaws-agent register` with the token. ### Air-gapped environments For environments without internet access during installation, the agent supports an offline install path. The control plane provides a bundle of the PowerShell modules the agent needs; you pre-fetch them, copy them to the machine, and pass `--modules-path` to the installer. This was a frequent pain point in earlier versions and is now a first-class path. > **// Outbound only - The agent never accepts inbound connections.** > > It opens an outbound HTTPS connection to the control plane and waits for work. That makes the firewall story simple: allow outbound HTTPS to `app.jawsdeploy.net` (or your Stack URL). No inbound ports on the target. ## Setting Up a Proxy for Stack Source: https://www.jawsdeploy.net/guides/setting-up-proxy-for-stack | Section: Self-Hosting & Admin TLS, host headers, WebSocket forwarding, and the few proxy settings that actually matter. Jaws Deploy Stack binds to plain HTTP by default. Production installs put Stack behind a reverse proxy that terminates TLS and forwards the requests. Almost any proxy works - nginx, IIS, Apache, HAProxy. ### What the proxy needs to do Three things, in order of how often they trip people up: ### Get these right and Stack works - **Forward the `Host` header** as-is. Stack uses it to build absolute URLs in emails and links. - **Forward `X-Forwarded-Proto`** so Stack knows requests are arriving over HTTPS. Without this, generated links use `http://`. - **Allow WebSocket upgrades.** Live deployment logs use WebSockets. Without upgrade support, logs degrade to slow polling. - **Bump body size limits.** Package uploads can be hundreds of MB; the proxy's default request size is usually too small. - **Disable response buffering on `/api/.../logs/stream`** so live logs stream rather than chunk. ### Minimal nginx config that ticks the boxes Replace `deploy.acme.internal` with your hostname and point `proxy_pass` at the Stack bind address. ``` server { listen 443 ssl http2; server_name deploy.acme.internal; ssl_certificate /etc/ssl/deploy.crt; ssl_certificate_key /etc/ssl/deploy.key; client_max_body_size 2g; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 600s; } location /api/v1/deployments/stream { proxy_pass http://127.0.0.1:8080; proxy_buffering off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` > **// Public URL config - Set `server.publicUrl` in Stack's config to the proxy's externally visible URL.** > > Stack uses this for absolute links in emails, REST API responses, and the agent registration UI. If it's wrong, agents will be told to connect to an unreachable address. ## Adding OIDC to Stack Source: https://www.jawsdeploy.net/guides/adding-oidc-to-stack | Section: Self-Hosting & Admin Wire your identity provider once. From then on, login goes through your SSO and Stack reads the claims. Jaws Deploy Stack supports OIDC for user authentication. Once configured, the login page redirects to your identity provider; Stack reads the returned claims to identify the user and (optionally) map group claims to Stack permissions. ### Configuration shape OIDC settings live in `config.yaml` under the `auth.oidc` section. The five values you need from your provider: issuer URL, client ID, client secret, the scopes to request, and the claim names for username/email/groups. ### OIDC against Microsoft Entra ID Replace tenant ID and client values with yours. Register the redirect URI `https://deploy.acme.internal/auth/oidc/callback` in Entra. ``` auth: oidc: enabled: true issuer: "https://login.microsoftonline.com//v2.0" clientId: "" clientSecret: "" scopes: ["openid", "profile", "email", "groups"] claims: username: "preferred_username" email: "email" groups: "groups" groupRoleMapping: "deploy-admins": "admin" "deploy-engineers":"deployer" "deploy-readonly": "viewer" ``` ### Group-to-role mapping The `groupRoleMapping` block translates IdP group claims into Stack roles. Users with the `deploy-admins` group become admins; users with `deploy-engineers` get deployer rights; others fall to viewer. Users not in any mapped group get no Stack access at all. > **// Local accounts after enabling OIDC - Keep one local admin as a break-glass account.** > > If OIDC misconfigures - wrong tenant, expired secret, IdP outage - you don't want to be locked out of Stack. Keep one local admin user with a strong password, document it, and audit its usage. ## Service Accounts and Automation Source: https://www.jawsdeploy.net/guides/service-accounts-and-automation | Section: Self-Hosting & Admin A service account is an identity for software. Each one has scoped permissions and a rotatable API key. A service account is the identity CI tools and automation scripts use to call the Jaws Deploy REST API. It's distinct from a human user: no UI login, no email, no group membership - just an ID, an API key, and a scoped permission set. ### Creating one In **Workspace -> Service accounts**, click **New**. Give it a name that describes its job (`teamcity-checkout-build`, `github-actions-platform`). Pick the permissions it needs - usually `releases.create` for a CI integration, plus `deployments.create` if the integration also triggers deployments. ### API keys Each service account can hold one or more API keys. Keys are rotatable independently - rotate by creating a new one, distributing it to consumers, and revoking the old one. Keys are shown once at creation and never displayed again. ### Same pattern works for any CI tool The API key is stored in the CI tool's secrets store, never in source. ``` Connect-JawsDeploy ` -Url "https://deploy.acme.internal" ` -Account "teamcity-checkout-build" ` -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "$buildNumber" ``` > **// One service account per integration - Don't share keys across pipelines.** > > A separate service account per CI integration means revoking one key affects exactly one consumer. It also makes the audit log readable: "who created this release?" gives you the specific pipeline, not a generic shared account. ## Migrating from Octopus Deploy Source: https://www.jawsdeploy.net/guides/migrating-from-octopus-deploy | Section: Migration & Comparisons The mental model maps closely. The differences are mostly in scope, pricing, and the size of the install. Teams migrating from Octopus Deploy find the mental model familiar. Projects, environments, lifecycles, releases, channels, targets, tags, variables - the vocabulary maps almost one-to-one. The work of a migration is mostly mechanical: moving definitions across, retiring integrations, and re-pointing CI. ### What maps cleanly Most of it. ### Octopus -> Jaws Deploy mapping - **Project**: Same concept. One project = one deployable unit, owns its process and variables. - **Environment**: Same. Dev, Staging, Production - scope for variables and step targeting. - **Lifecycle**: Same. Ordered phases that releases must walk through. - **Release**: Same. Immutable snapshot of packages, variables, and process. - **Tentacle / Agent**: Octopus Tentacle ≈ Jaws Deploy Agent. Outbound connection to the control plane. - **Tags / Roles**: Octopus roles map onto Jaws Deploy tags. Same scoping mechanic. ### What changes shape A few things are deliberately smaller or different. **Workspaces** replace Octopus Spaces with a similar model but lighter access controls. **Step templates** are simpler - less metadata, fewer parameter types. **Variable sets** as separate first-class objects are absent; the variable scoping mechanism in Jaws Deploy is direct and replaces most of what variable sets were used for. > **// Pricing model - The pricing shape is different - per-machine target rather than per-deployment-target-instance.** > > For most teams this works out cheaper, especially with many small services on a shared set of machines. The pricing page has the current numbers; reach out if you're sizing a multi-hundred-target install. ### A practical migration path The shortest path that's worked for several teams: pick one Octopus project, recreate it manually in Jaws Deploy (the platform is small enough that the recreate-by-hand version is often faster than tooling an import), run both in parallel for a release cycle, point CI at the new project, retire the Octopus project. Repeat. Do not try to migrate everything in one weekend - the parallel-run discipline is what catches the deployment-process subtleties that aren't visible in a structured export. # REST API reference ## Introduction Source: https://www.jawsdeploy.net/rest-api/introduction | Section: Getting Started What the Jaws Deploy REST API is for, and how the docs are organized. The Jaws Deploy REST API lets your build server, CLI, or platform tooling drive the same workflows you can do in the web UI: creating releases, deploying them, promoting them through environments, and managing the underlying topology (projects, environments, lifecycles, feeds, tags, variables, step templates, script modules, cloud accounts). ### Base URL All endpoints accept and return JSON unless otherwise noted (the package upload endpoint takes `multipart/form-data`). ``` https://app.jawsdeploy.net/api ``` ### What is documented here Every public endpoint in the platform's `Api` namespace decorated with service-account authentication is listed in the sidebar, grouped by resource. Internal agent-side endpoints (deployment monitor, agent handshake, package downloads) are intentionally not part of this reference. ### Conventions - IDs in request and response bodies are opaque strings (GUIDs or short tokens). - `workspaceId`, `projectId`, etc. always refer to resources visible to the authenticated service account. - Successful mutations return `200 OK` with either an empty body or a small response object containing the new resource's ID. - Validation failures return `400 Bad Request` with `{ errorcode, error }` and (where applicable) a `validationErrors` map. - Authorization failures return `401 Unauthorized` with the same shape. ## Authentication Source: https://www.jawsdeploy.net/rest-api/authentication | Section: Getting Started How service accounts authenticate against the REST API. All public REST API endpoints require a **service account** with an API key. Service accounts are created in the Jaws Deploy UI and scoped to a workspace with explicit roles (deploy, manage workspace, push packages, etc.). ### Basic auth header Send the credentials as HTTP Basic auth: ``` Authorization: Basic ``` Example with `curl`: ``` curl -u $JAWS_SA_ID:$JAWS_API_KEY \ https://app.jawsdeploy.net/api/environment?workspaceId=$WS ``` ### Permission model Every endpoint runs through a guard derived from the service account's roles. If the credentials are valid but the account is missing a required role, you get `401 Unauthorized` with `errorcode = InsufficientPermissions` and a message describing the missing capability. ### Tips - Create one service account per integration. Don't share keys across CI jobs. - Scope each service account to the smallest set of workspaces it needs. - Rotate API keys when staff with access changes. ## Who am I Source: https://www.jawsdeploy.net/rest-api/who-am-i | Section: Getting Started Return the service account the request authenticated as, and the organization it belongs to. `GET /api/me` Returns the service account the request authenticated as, and the organization it belongs to. It takes no parameters and changes nothing. This is the endpoint to point a new integration at first. A `200` here proves three things at once: the credentials are well formed, they are still active, and they resolve to the organization you expected. That last one is the useful part - it catches a pipeline wired up with the wrong environment's API key *before* it creates a release in somebody else's workspace, which is otherwise a mistake you find out about afterwards. Every other endpoint in this reference authenticates exactly the same way, so if this call fails none of them will work either. That makes it the right thing to run first when something that used to work has stopped: it separates "the credentials are wrong" from "the request is wrong". See [Authentication](https://www.jawsdeploy.net/rest-api/authentication) for how the header is built, and [Errors and validation](https://www.jawsdeploy.net/rest-api/errors) for what a failure looks like. #### What the fields fall back to `serviceAccountId` and `organizationId` come straight from the authenticated credentials and are always present. They are the two values worth asserting on in a pipeline. The two names are looked up rather than carried, and each one has a fallback: - `serviceAccountName` is the service account's display name, falling back to the `serviceAccountId` when no user record resolves. A response in which the name and the id are identical therefore means the lookup found nothing - not that somebody named the account after its own id. - `organizationName` falls back to the organization name recorded on the service account, and is `null` when neither resolves. `message` is a greeting assembled from `serviceAccountName`. It exists to make a manual `curl` readable and carries nothing the other fields do not. Do not parse it - it is the one field here whose wording is free to change. #### What it does not tell you A `200` means the API answered and your credentials are good. It is not a service health check: it says nothing about whether agents are connected, whether a deployment slot is free, or whether anything can currently be deployed. Nor does it describe what the service account is *allowed* to do - roles are enforced per endpoint, so a call that succeeds here can still come back `401` from [Create a release](https://www.jawsdeploy.net/rest-api/releases-create) for want of a permission. ### Response fields | Field | Type | Description | |---|---|---| | `message` | string | A human-readable greeting built from `serviceAccountName`, for eyeballing a manual request. Not machine-readable - do not parse it. | | `serviceAccountId` | string | The id of the authenticated service account - the same value used as the username half of the Basic auth pair. Always present. | | `serviceAccountName` | string | The account's display name, falling back to `serviceAccountId` when no user record resolves. | | `organizationId` | string | The organization the credentials belong to. Always present, and the value to assert on when you want to be sure which tenant a pipeline is pointed at. | | `organizationName` | string | That organization's name, or `null` when it cannot be resolved. | ### Errors | Status | Meaning | |---|---| | 401 | Missing or invalid Basic auth credentials. There is no other failure mode - this endpoint takes no input to get wrong. | Example request: ``` GET /api/me HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic ``` Example response: ``` { "message": "Hello, Build agent!", "serviceAccountId": "usr_ci", "serviceAccountName": "Build agent", "organizationId": "org_acme", "organizationName": "Acme Corp" } ``` ## Errors and validation Source: https://www.jawsdeploy.net/rest-api/errors | Section: Getting Started How error responses are shaped and how to read them. Every error response carries a small JSON body: ``` { "errorcode": "InvalidParameter", "error": "invalid project ID" } ``` ### Common error codes | Code | Meaning | |------|---------| | `InvalidParameter` | A required field is missing, malformed, or references a resource the caller can't see. | | `InsufficientPermissions` | The service account is authenticated but doesn't hold the required role. | | `UnknownError` | Reserved for the rare unexpected failure path. | ### Validation errors Endpoints that run DTO-level validation (FluentValidation) respond with `400` and a `validationErrors` object keyed by field name with an array of messages, in addition to the base `errorcode` / `error` pair. ### Resource limit conflicts Deploy and promote endpoints can fail with `409 Conflict` and `errorcode = ResourceUsageLimitExceeded` plus a `resourceUsageErrors` array when the deploying organization has hit a plan limit. ## Workspace, projects, environments Source: https://www.jawsdeploy.net/rest-api/working-with-resources | Section: Getting Started How the core resource graph fits together when calling the API. The Jaws Deploy resource graph is straightforward: - An **organization** owns one or more **workspaces**. - Each **workspace** holds the deployment topology: **projects**, **environments**, **feeds**, **lifecycles**, **tags**, **cloud accounts**, **step templates**, **script modules**, **workspace variables**. - Each **project** has its own **channels**, **steps**, **variables**, and **releases**. - A **release** is an immutable snapshot tied to a project, containing chosen package versions and the deployment plan at creation time. **Deployments** execute a release against one or more environments. ### Typical CI/CD call sequence 1. Upload built artifacts to a workspace feed: `POST /api/packagestore/package`. 2. Create a release: `POST /api/release`. 3. Deploy or promote it: `POST /api/release/deploy` or `POST /api/release/promote`. 4. Poll deployment status: `GET /api/deployment`. Topology management endpoints (projects, environments, lifecycles, etc.) are available for teams that prefer to keep infrastructure-as-code in their own repos. ## Package types and naming Source: https://www.jawsdeploy.net/rest-api/package-types | Section: Getting Started Supported package formats and the filename convention. Jaws Deploy stores deployment artifacts as packages in workspace feeds. The [package upload endpoint](https://www.jawsdeploy.net/rest-api/package-store-upload) (`POST /api/packagestore/package`) accepts the following formats: - .nupkg (NuGet) - .zip - .tar - .tar.gz ### Filename convention The uploader parses the package ID and version directly from the filename. Use: ``` .. ``` Examples: ``` Acme.Web.2.5.3.nupkg Acme.Worker.2.5.3-rc1.zip web-frontend.2026.05.16+sha.abcdef.tar.gz ``` The version segment must be a valid SemVer / NuGetVersion string. Filenames that don't parse get rejected with `400 Bad Request`. See [Upload a package](https://www.jawsdeploy.net/rest-api/package-store-upload) for the request itself - the feed the package lands in, the multipart body, and the responses. ## List releases Source: https://www.jawsdeploy.net/rest-api/releases-list | Section: Releases Page through a project's releases, newest first, with the notes and metadata of each one. `GET /api/release` Returns one page of the releases of a project, newest first. Every entry carries the release's identity, its notes and the [metadata document](https://www.jawsdeploy.net/rest-api/releases-create) attached when it was created, so a pipeline polling for what has been built does not have to read each release individually to see them. What is deliberately **not** here is the snapshot - the package versions, steps, variables and lifecycle phases a release locked in. That is what [Get a release](https://www.jawsdeploy.net/rest-api/releases-details) returns. The deployments of a release are not here either: a release can be deployed any number of times, so its deployments are a paged list of their own - [List deployments](https://www.jawsdeploy.net/rest-api/deployments-list). Ordering is by **creation date**, newest first, with the release ID breaking ties between two releases created in the same microsecond. It is not version order: SemVer ordering cannot be expressed in SQL, and a page boundary has to mean the same thing to the server as it does to the caller. A release cut out of version sequence - a hotfix on an older line, say - therefore still appears at the top of the first page. Deleted releases are never returned. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project whose releases to list, from [List projects](https://www.jawsdeploy.net/rest-api/projects-list). | | `max` | query | integer | no | Releases per page. Defaults to `100` and is clamped to the range 1-500 rather than rejected, so an out-of-range value still returns a page. | | `cursor` | query | string | no | Continuation token from the previous response's `nextCursor`. Omit it to read the first page. See below. | #### Paging Paging is by cursor, not by row offset. Read the first page without `cursor`, then hand each response's `nextCursor` back as the `cursor` of the next request, and keep going until `nextCursor` comes back `null`. That `null` is the only signal that you have read everything. Do not stop on a short page and do not compare the row count against the `max` you asked for - neither tells you whether more releases follow. A cursor rather than a page number, because a build server keeps creating releases while you page and each new one arrives at the *top* of this list. A numbered window would shift underneath you on every arrival, handing you some releases twice and skipping others entirely. A cursor names the last row you actually read, so the next page continues from there whatever has appeared in front of it. The token is opaque. Hand it back as you received it rather than building or reading one - it encodes a position, and the encoding is free to change. A token that does not decode is rejected with `400` rather than ignored, because serving page one to a caller that believes it asked for page nine is the worse failure. ### Response fields | Field | Type | Description | |---|---|---| | `projectId` | string | The project the page belongs to, echoing the one you asked for. | | `releases` | object[] | One page of releases, newest first. Empty array when the project has no releases - not an error. | | `releases[].releaseId` | string | The release's id. Pass it to [Get a release](https://www.jawsdeploy.net/rest-api/releases-details) or [Deploy a release](https://www.jawsdeploy.net/rest-api/releases-deploy). | | `releases[].projectId` | string | The project the release belongs to. | | `releases[].version` | string | The release version, as it was stored. Note that this list is *not* ordered by it. | | `releases[].notes` | string | The release notes, or `null` when none were given. | | `releases[].metadata` | object | The free-form document attached at creation, exactly as it was stored, or `null` when none was attached. Jaws never reads into it - see [Create a release](https://www.jawsdeploy.net/rest-api/releases-create). | | `releases[].created` | string | When the release was created, ISO 8601 with offset. This is the field the list is ordered by. | | `releases[].createdBy` | object | Who created the release: `userId`, `name` and `email`. The service account's own user for a release cut by a pipeline. `null` when the creator no longer resolves. | | `releases[].channelId` | string | The channel the release is bound to, or `null` for a release created without one. | | `releases[].channelName` | string | Name of that channel, or `null`. | | `releases[].lifecycleName` | string | Name of the lifecycle the channel binds the release to, or `null` when the release is not phase-controlled. | | `nextCursor` | string | Pass back as `cursor` to read the next page. `null` means this page was the last one. | ### Errors | Status | Meaning | |---|---| | 400 | Missing or invalid `projectId`. A project in another organization is reported the same way as one that does not exist, so an id cannot be probed for existence. | | 400 | `cursor` is not a token this endpoint issued - the response reads `invalid cursor`. Nothing is returned; ask again from the page you last read. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to view releases of this project. | Example request: ``` GET /api/release?projectId=prj_abc123&max=50 HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic ``` Example response: ``` { "projectId": "prj_abc123", "releases": [ { "releaseId": "rel_9f4c", "projectId": "prj_abc123", "version": "2.5.3", "notes": "Build #4821 from main", "metadata": { "buildUrl": "https://ci.example/build/4821", "commit": "3f9a1c2", "workItems": [ { "id": "JAWS-14", "title": "Fix the retry loop" } ] }, "created": "2026-09-05T09:14:22.418+00:00", "createdBy": { "userId": "usr_ci", "name": "Build agent", "email": "ci@example.com" }, "channelId": "chn_stable", "channelName": "Stable", "lifecycleName": "Default lifecycle" } ], "nextCursor": "NjM4OTM5MzI0NjI0MTgwMDAwfHJlbF85ZjRj" } ``` ## Get a release Source: https://www.jawsdeploy.net/rest-api/releases-details | Section: Releases Read one release with the snapshot it will deploy - packages, steps, variables and lifecycle phases. `GET /api/release/details` Returns a single release together with the snapshot it locked in when it was created: the package versions chosen, the deployment plan (steps) as it stood, the variables as they stood, and where the release currently sits in its lifecycle. There are **two ways to name the release**, because both are what a caller actually holds: - `releaseId` - what [Create a release](https://www.jawsdeploy.net/rest-api/releases-create) handed back to the job that cut it. - `projectId` together with `version` - what a later job usually knows, having only ever asked for a version. Give one or the other. Sending neither is a `400`. `version` is matched exactly first and then as a SemVer equivalent, so `6.1` finds a release stored as `6.1.0` and a caller does not have to guess which spelling the build system used. Deployments are not part of this response. A release can be deployed any number of times, so its deployments are a paged list in their own right - [List deployments](https://www.jawsdeploy.net/rest-api/deployments-list) - and the status and logs of a single deployment are on [Get deployment status](https://www.jawsdeploy.net/rest-api/deployments-status). Parts of the snapshot are secrets and do not leave the server - see below. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `releaseId` | query | string | no | ID of the release. Give this, **or** `projectId` and `version` together. | | `projectId` | query | string | no | ID of the project. Only meaningful together with `version`, and ignored when `releaseId` is given. | | `version` | query | string | no | The release version within that project. Only meaningful together with `projectId`. Matched exactly first, then as a SemVer equivalent - `6.1` finds `6.1.0`. | #### Secrets in the snapshot A release snapshot holds things that must not leave the server, and three of them are withheld here. There is no parameter that turns any of it off. - **Variable values** are masked whenever the snapshot encrypted them or the variable is typed `Secret`. The value reads back as •••••• with `isSecret` set to `true` - the same test and the same mask the rest of Jaws applies before showing a value. - **Step scripts** are omitted entirely. A script can carry a literal credential, and a full script body would dominate the response besides. `hasScript` says whether the step has one. - **Step property values** are omitted entirely. Step template properties include `SecureString` controls, and nothing here could reliably tell a credential apart from an ordinary setting. `hasProperties` says whether the step has any. #### Step fields A release step is a copy of the project step as it stood when the release was cut, so changing the project afterwards does not change what this release will run. `runOn` is `TargetMachine`, `Worker` or `WorkerToCloudTargets`. `scriptLanguage` is `powershell` (PowerShell 7), `powershell5` (Windows PowerShell 5.1), `python` or `json`. `ignoreErrors` is the snapshot of the project step's `errorAction`: `true` is `Continue`, `false` is `Stop`. `onStepFailure` (`ContinueToNextStep` or `StopDeployment`) and `runAfterStop` are the separate question of what a failed step does to the rest of the deployment - see [Update a project step](https://www.jawsdeploy.net/rest-api/project-step-update) for how the two interact. `machineOrder` (`MachineName` or `TagPriority`), `onMachineFailure` (`ContinueToOtherMachines` or `StopStep`) and `waitBetweenMachineGroups` describe the rolling behaviour; `isRolling` and `rollingGroupId` say whether the step is part of a [rolling group](https://www.jawsdeploy.net/rest-api/rolling-groups-list), and that id names the snapshotted group, not the project's. #### Lifecycle phases `phases` is empty for a release whose channel binds no lifecycle - such a release can be deployed to any environment its project reaches. Where there is a lifecycle, each phase reports whether it `isAvailable` (the release may be deployed into it now), whether it `isCompleted`, whether it `isOptional`, and how many of its `deploymentsRequired` have been completed so far. `howToTrigger` on a phase environment is `Manual` or `Automatic` - `Automatic` is an environment a completed preceding phase deploys into on its own. This is the same progression that decides whether a deploy is allowed, so reading it first tells you what [Deploy a release](https://www.jawsdeploy.net/rest-api/releases-deploy) will accept: an environment blocked by phase progression is refused there with `some environments are not available`. ### Response fields | Field | Type | Description | |---|---|---| | `releaseId` | string | The release's id. | | `projectId` | string | The project the release belongs to. | | `projectName` | string | That project's name as it reads now, not as it read when the snapshot was taken. | | `workspaceId` | string | The workspace the project belongs to. | | `version` | string | The release version, as it was stored. | | `notes` | string | The release notes, or `null`. | | `metadata` | object | The free-form document attached at creation, exactly as it was stored, or `null`. See [Create a release](https://www.jawsdeploy.net/rest-api/releases-create). | | `created` | string | When the release was created, ISO 8601 with offset. | | `createdBy` | object | Who created it: `userId`, `name`, `email`. `null` when the creator no longer resolves. | | `channelId` | string | The channel the release is bound to, or `null`. | | `channelName` | string | Name of that channel, or `null`. | | `lifecycleName` | string | Name of the lifecycle the channel binds the release to, or `null` when the release is not phase-controlled. | | `packages` | object[] | The package versions the release pinned. Empty when the project has no packages. | | `packages[].feedId` | string | The feed the package is pulled from. | | `packages[].packageId` | string | The package id. | | `packages[].packageType` | string | The package type - see [Package types and naming](https://www.jawsdeploy.net/rest-api/package-types). | | `packages[].version` | string | The exact version pinned into this release. | | `steps` | object[] | The deployment plan as it stood when the release was cut, ordered by `order`. | | `steps[].releaseStepId` | string | Id of the snapshotted step. Distinct from the project step it was copied from. | | `steps[].originalStepId` | string | Id of the project step it was copied from, which may since have changed or been deleted. | | `steps[].order` | integer | Position in the plan. Steps are returned in this order. | | `steps[].name` | string | Step name. This is what [Deploy a release](https://www.jawsdeploy.net/rest-api/releases-deploy) matches `excludeStepNames` against. | | `steps[].description` | string | Step description, or `null`. | | `steps[].runOn` | string (enum) | `TargetMachine`, `Worker` or `WorkerToCloudTargets`. | | `steps[].scriptLanguage` | string (enum) | `powershell`, `powershell5`, `python` or `json`. | | `steps[].hasScript` | boolean | Whether the step has a script body. The body itself is never returned. | | `steps[].hasProperties` | boolean | Whether the step has step-template property values. The values themselves are never returned. | | `steps[].ignoreErrors` | boolean | The snapshot of the step's `errorAction`: `true` is `Continue`, `false` is `Stop`. | | `steps[].environmentIds` | string[] | Environments the step is restricted to. Empty means every environment of the deployment. | | `steps[].machineNameFilter` | string[] | Machine filter carried into the snapshot. Empty means no filter. | | `steps[].workerTagId` | string | Tag identifying the worker pool, or `null`. | | `steps[].parallelMachines` | integer | How many machines the step runs on at once. | | `steps[].parallelCloudTargets` | integer | How many cloud targets the step runs on at once, or `null`. | | `steps[].isRolling` | boolean | Whether the step is part of a rolling group. | | `steps[].rollingGroupId` | string | The snapshotted rolling group the step belongs to, or `null`. Not the project's group id. | | `steps[].machineOrder` | string (enum) | `MachineName` or `TagPriority`. | | `steps[].onMachineFailure` | string (enum) | `ContinueToOtherMachines` or `StopStep`. | | `steps[].onStepFailure` | string (enum) | `ContinueToNextStep` or `StopDeployment`. | | `steps[].runAfterStop` | boolean | Whether the step still runs after an earlier step stopped the deployment. | | `steps[].waitBetweenMachineGroups` | boolean | Whether each tag rank finishes before the next one starts. | | `variables` | object[] | The variables as they stood when the release was cut, ordered by name. Secret values are masked. | | `variables[].name` | string | Variable name, as referenced from scripts and step properties. | | `variables[].description` | string | Variable description, or `null`. | | `variables[].type` | string | `Text`, `Secret`, `Script`, `Number`, `Boolean`, `Date`, `Map` or `Json`. | | `variables[].values` | object[] | The scoped values of the variable. | | `variables[].values[].value` | string | The value, or the mask when it is a secret. | | `variables[].values[].isSecret` | boolean | `true` when the value was masked - set when the snapshot encrypted it or the variable is typed `Secret`. | | `variables[].values[].environmentsFilter` | string[] | Environments this value applies to. Empty means all. | | `variables[].values[].machineFilter` | string[] | Machines this value applies to. Empty means all. | | `variables[].values[].cloudTargetFilter` | string[] | Cloud targets this value applies to. Empty means all. | | `variables[].values[].stepFilter` | string[] | Steps this value applies to. Empty means all. | | `phases` | object[] | Where the release sits in its lifecycle, ordered by `sortOrder`. Empty when the release is not phase-controlled. | | `phases[].phaseId` | string | Id of the snapshotted phase. | | `phases[].name` | string | Phase name. | | `phases[].sortOrder` | integer | Position in the lifecycle. Phases are returned in this order. | | `phases[].isAvailable` | boolean | Whether the release may be deployed into this phase's environments now. | | `phases[].isCompleted` | boolean | Whether the phase's progress requirement has been met. | | `phases[].isOptional` | boolean | Whether the phase can be skipped without blocking the ones after it. | | `phases[].isAnyDeploymentRunning` | boolean | Whether a deployment into this phase is in flight, or `null` when that is not known. | | `phases[].deploymentsRequired` | integer | How many completed deployments the phase needs before it counts as completed. | | `phases[].deploymentsCompleted` | integer | How many it has had. | | `phases[].environments` | object[] | The environments in the phase. | | `phases[].environments[].environmentId` | string | The environment's id. | | `phases[].environments[].environmentName` | string | The environment's name. | | `phases[].environments[].howToTrigger` | string (enum) | `Manual` or `Automatic`. An `Automatic` environment is deployed into on its own once the preceding phase completes. | | `phases[].environments[].deploymentsCompleted` | integer | How many deployments of this release into this environment have completed. | ### Errors | Status | Meaning | |---|---| | 400 | Neither a `releaseId` nor a `projectId` and `version` pair was given - the response reads `a release ID, or a project ID together with a version, is required`. | | 400 | Invalid `releaseId`. A release in another organization is reported the same way as one that does not exist, so an id cannot be probed for existence. | | 400 | Invalid `projectId`, or no release with that `version` exists for the project. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to view releases of this project. | Example request: ``` GET /api/release/details?releaseId=rel_9f4c HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic // or, when only the version is known: GET /api/release/details?projectId=prj_abc123&version=2.5.3 HTTP/1.1 ``` Example response: ``` { "releaseId": "rel_9f4c", "projectId": "prj_abc123", "projectName": "Acme Web", "workspaceId": "ws_abc", "version": "2.5.3", "notes": "Build #4821 from main", "metadata": { "buildUrl": "https://ci.example/build/4821", "commit": "3f9a1c2" }, "created": "2026-09-05T09:14:22.418+00:00", "createdBy": { "userId": "usr_ci", "name": "Build agent", "email": "ci@example.com" }, "channelId": "chn_stable", "channelName": "Stable", "lifecycleName": "Default lifecycle", "packages": [ { "feedId": "fd_internal", "packageId": "Acme.Web", "packageType": "nuget", "version": "2.5.3" } ], "steps": [ { "releaseStepId": "rstp_01", "originalStepId": "stp_01", "order": 1, "name": "Deploy website", "description": null, "runOn": "TargetMachine", "scriptLanguage": "powershell", "hasScript": true, "hasProperties": true, "ignoreErrors": false, "environmentIds": [], "machineNameFilter": [], "workerTagId": null, "parallelMachines": 2, "parallelCloudTargets": null, "isRolling": true, "rollingGroupId": "rrg_web", "machineOrder": "MachineName", "onMachineFailure": "StopStep", "onStepFailure": "StopDeployment", "runAfterStop": false, "waitBetweenMachineGroups": false } ], "variables": [ { "name": "ConnectionString", "description": "Database connection", "type": "Secret", "values": [ { "value": "••••••", "isSecret": true, "environmentsFilter": ["env_prod"], "machineFilter": [], "cloudTargetFilter": [], "stepFilter": [] } ] } ], "phases": [ { "phaseId": "rph_01", "name": "Test", "sortOrder": 1, "isAvailable": true, "isCompleted": true, "isOptional": false, "isAnyDeploymentRunning": false, "deploymentsRequired": 1, "deploymentsCompleted": 1, "environments": [ { "environmentId": "env_test", "environmentName": "Test", "howToTrigger": "Automatic", "deploymentsCompleted": 1 } ] } ] } ``` ## Create a release Source: https://www.jawsdeploy.net/rest-api/releases-create | Section: Releases Snapshot package versions and the current deployment plan into a new release. `POST /api/release` Creates a new release for a project. A release locks in the project's deployment plan (steps), the chosen package versions, and the project channel - everything needed to redeploy the same thing later. If `version` is omitted, the server proposes the next version using the project's release versioning rules. If `packageVersions` is omitted, the latest version of every project package is selected by default. If `channelName` is omitted the project's default channel is used (unless `ignoreDefaultChannel = true`). #### Channel version rules If the resolved channel has [version rules](https://www.jawsdeploy.net/guides/channel-version-rules), they are enforced here, and they shape the defaults: - **The proposed version respects the channel.** When `version` is omitted the proposal is seeded from the releases already in that channel - so a stable and a prerelease stream keep independent numbering - then snapped up to the minimum of the channel's version range and given the channel's default prerelease tag. - **Default package versions respect the channel.** For any package not named in `packageVersions`, the newest version the channel *allows* is pinned, rather than the newest version that exists. - **Explicit values are validated, never silently corrected.** A `version` or a pinned package version the channel disallows fails the request with a message naming the channel and the reason. The channel and its rules are always loaded server-side from the channel resolved for this request, so a caller cannot influence which rules apply. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project to create the release for. | | `channelName` | body | string | no | Channel name to bind the release to. Defaults to the project's default channel. The channel's version rules are enforced on this request. | | `ignoreDefaultChannel` | body | boolean | no | Set to `true` to skip the project's default channel when `channelName` is omitted. The release is then created without a channel, and no channel rules apply. | | `version` | body | string | no | Explicit SemVer version. Defaults to the next proposed version for the resolved channel. Must satisfy the channel's release version rule. | | `notes` | body | string | no | Free-form release notes. | | `packageVersions` | body | object | no | Map of `packageId -> version` to pin specific package versions. Each pinned version must satisfy whichever channel package rule governs that package. Packages left out default to the newest version the channel allows. | | `metadata` | body | object | no | Free-form JSON object carried with the release - work items, the commit it was built from, a ticket reference. Stored as given and returned by every release read. See below. | #### Release metadata `metadata` is a free-form JSON document the release carries. Jaws stores it and gives it back on every release read - [List releases](https://www.jawsdeploy.net/rest-api/releases-list) and [Get a release](https://www.jawsdeploy.net/rest-api/releases-details) both return it - and never interprets a single field of it. That is the whole contract, which is why the only rules are about shape and size. - It must be a **JSON object**. An array, a string, a number or a boolean at the top level is refused with `metadata must be a JSON object`. An object is the only shape that stays extensible: a caller that starts with a `buildUrl` can add `workItems` later without either side renegotiating. - `{}` is accepted and stored. Omitting the field, or sending JSON `null`, means the release has no metadata, and `metadata` reads back as `null`. - The serialized document must be at most **64 KB** - 65536 bytes of UTF-8. A larger one is refused with a message naming the size it actually was. Everything inside is carried through untouched: nesting to any depth, nulls, booleans, numbers and array order all come back as they went in. No field is dropped, renamed or validated, so there is no schema to keep in step with Jaws. Two things are **not** preserved, because the document is stored in a JSON column: the order of an object's keys, and whitespace. Compare metadata field by field rather than by string equality, and do not expect to read the document back byte for byte. Metadata belongs to the release from the moment it is created. No endpoint edits it afterwards, so anything a pipeline learns after the release was cut has to go somewhere else. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`, invalid `channelName`, or pinned packages with versions that don't exist. | | 400 | `version` is not allowed in the resolved channel - the message names the channel and whether the version range or the prerelease tag rejected it. | | 400 | One or more pinned package versions are not allowed in the resolved channel - the message lists each rejected `packageId @ version` with the reason. | | 400 | No available version of a project package satisfies the channel's rules, so there is nothing to pin for it. Push a compliant package version, or create the release in a different channel. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | | 400 | Validation failed - the response includes `errorcode = InvalidParameter` and (where applicable) a `validationErrors` map. | | 400 | `metadata` is not a JSON object - the response reads `metadata must be a JSON object`. No release is created. | | 400 | `metadata` is larger than 64 KB once serialized - the message names the size it actually was. No release is created. | Example request: ``` POST /api/release HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic Content-Type: application/json { "projectId": "prj_abc123", "channelName": "Beta", "version": "2.5.3-beta1", "notes": "Build #4821 from main", "packageVersions": { "Acme.Web": "2.5.3-beta1", "Acme.Worker": "2.5.3-beta1" }, "metadata": { "buildUrl": "https://ci.example/build/4821", "commit": "3f9a1c2", "workItems": [ { "id": "JAWS-14", "title": "Fix the retry loop" } ] } } ``` Example response: ``` { "releaseId": "rel_9f4c..." } ``` ## Deploy a release Source: https://www.jawsdeploy.net/rest-api/releases-deploy | Section: Releases Run an existing release against one or more environments. `POST /api/release/deploy` Deploys an existing release. You can target an environment by name, a lifecycle phase, or pass a structured `environments` list with per-environment machine rules. Returns the new deployment IDs (one per environment). The deployment runs asynchronously - poll `GET /api/deployment` for status. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `releaseId` | body | string | yes | ID of the release to deploy. | | `environmentName` | body | string | no | Single environment to deploy to (alternative to `environments`). | | `phaseName` | body | string | no | Deploy to every environment in the given lifecycle phase. | | `environments` | body | array | no | List of `{ environmentName, machineMode, machineIds? }` setups for fine-grained control. | | `redownloadPackages` | body | boolean | no | Force agents to redownload packages even if cached. | | `deploymentDateUnixMillis` | body | integer | no | Schedule the deployment for a future time (UTC unix millis). | | `excludeStepNames` | body | array | no | Skip these step names from the deployment. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `releaseId`, no matching environments, or validation failure. | | 400 | One or more of the named environments is not available for this release - the message reads `some environments are not available (disabled or blocked by phase progression for this release)` and lists them. An environment is unavailable when it has been switched off with [Update an environment](https://www.jawsdeploy.net/rest-api/environments-update), or when the release has not reached its lifecycle phase yet. Check `enabled` on [List environments](https://www.jawsdeploy.net/rest-api/environments-list) to tell the two apart. | | 409 | Resource usage limit exceeded - response includes `resourceUsageErrors`. | | 401 | Missing or invalid Basic auth credentials, the service account may not deploy this project, or it may not deploy to one of the environments named - the last of those lists the environment IDs it refused. | Example request: ``` POST /api/release/deploy HTTP/1.1 Authorization: Basic Content-Type: application/json { "releaseId": "rel_9f4c...", "environmentName": "Staging" } ``` Example response: ``` { "deploymentId": "dep_a1b2c3", "deploymentIds": ["dep_a1b2c3"] } ``` ## Promote a release Source: https://www.jawsdeploy.net/rest-api/releases-promote | Section: Releases Promote the latest (or a specific) version of a project to one or more environments. `POST /api/release/promote` Promotes a project's release without needing to know its release ID. If `version` is omitted, the highest existing version is used - highest by SemVer across the whole project, which is not necessarily the most recently created release and does not take channels into account. The deployment payload is the same as `/api/release/deploy` aside from looking up the release by project and version. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project to promote. | | `version` | body | string | no | Specific version to promote, matched **exactly** as the release was created. Unlike [Get a release](https://www.jawsdeploy.net/rest-api/releases-details) and [List deployments](https://www.jawsdeploy.net/rest-api/deployments-list), this does *not* fall back to a SemVer equivalent - `6.1` will not find a release stored as `6.1.0`. That is deliberate: those two are reads, and this one starts a deployment, so a version it was not given exactly is refused rather than interpreted. Defaults to the highest existing version when omitted. | | `environmentName` | body | string | no | Single environment to deploy to. | | `phaseName` | body | string | no | Promote to every environment in the given lifecycle phase. | | `environments` | body | array | no | Per-environment machine rules - same shape as `/api/release/deploy`. | | `redownloadPackages` | body | boolean | no | Force agents to redownload packages. | | `deploymentDateUnixMillis` | body | integer | no | Schedule the deployment for a future time (UTC unix millis). | | `excludeStepNames` | body | array | no | Skip these step names from the deployment. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`, no release found to promote, no matching environments, or validation failure. | | 400 | One or more of the named environments is not available for this release - the message reads `some environments are not available (disabled or blocked by phase progression for this release)` and lists them. An environment is unavailable when it has been switched off with [Update an environment](https://www.jawsdeploy.net/rest-api/environments-update), or when the release has not reached its lifecycle phase yet. Check `enabled` on [List environments](https://www.jawsdeploy.net/rest-api/environments-list) to tell the two apart. | | 409 | Resource usage limit exceeded. | | 401 | Missing or invalid Basic auth credentials, the service account may not deploy this project, or it may not deploy to one of the environments named - the last of those lists the environment IDs it refused. | Example request: ``` POST /api/release/promote HTTP/1.1 Authorization: Basic Content-Type: application/json { "projectId": "prj_abc123", "environmentName": "Production" } ``` Example response: ``` { "deploymentId": "dep_d4e5f6", "deploymentIds": ["dep_d4e5f6"] } ``` ## List deployments Source: https://www.jawsdeploy.net/rest-api/deployments-list | Section: Deployments Page through the deployments of a workspace, a project or a single release - and poll for what has changed. `GET /api/deployment/list` Returns one page of deployments, most recently changed first. **Scope is required, and it is exactly one of** `workspaceId`, `projectId` or `releaseId`. Sending none of them, or more than one, is a `400` - there is no unscoped read of every deployment in the organization. Adding `version` to a `projectId` narrows the read to that one release, resolved the same way [Get a release](https://www.jawsdeploy.net/rest-api/releases-details) resolves it. Each entry is a summary: what was deployed where, by whom, when it last changed, and its status and error counts. The **logs are not here** - those are on [Get deployment status](https://www.jawsdeploy.net/rest-api/deployments-status), one deployment at a time. One deployment targets exactly one environment. A [deploy](https://www.jawsdeploy.net/rest-api/releases-deploy) or [promote](https://www.jawsdeploy.net/rest-api/releases-promote) that named three environments produced three deployments, and all three appear here separately. A workspace-wide read shows the projects this service account may see and **silently leaves the rest out**. Asking about a workspace is a fair question even when part of it is none of your business, so widening the scope never turns into a permission error over one project. Naming a project or a release you may not see is a different matter, and is refused. Optionally narrow any scope to selected environments with `environmentIds` or `environmentNames`. Omit both to keep the existing unfiltered behavior. See the environment filtering notes below. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | no | Every deployment of every project in this workspace that you are allowed to see. Give exactly one of `workspaceId`, `projectId` or `releaseId`. | | `projectId` | query | string | no | Every deployment of this project. Give exactly one of the three scope parameters. | | `releaseId` | query | string | no | Every deployment of this one release. Give exactly one of the three scope parameters. | | `version` | query | string | no | Narrows a `projectId` read to the release carrying this version. Only valid together with `projectId` - on its own it is a `400`. Matched exactly first, then as a SemVer equivalent, so `6.1` finds `6.1.0`. | | `changedSince` | query | string | no | ISO 8601 date and time, e.g. `2026-09-02T14:05:00Z`. Only deployments that changed strictly after it are returned, and the sort flips to oldest change first. See below. | | `max` | query | integer | no | Deployments per page. Defaults to `100` and is clamped to the range 1-500 rather than rejected. | | `cursor` | query | string | no | Continuation token from the previous response's `nextCursor`. Omit it to read the first page. See below. | | `environmentIds` | query | array | no | Optional environment IDs. Repeat the query key for multiple values, for example `environmentIds=env_prod&environmentIds=env_stage`. Matches any listed environment within the scoped workspace. Cannot be combined with `environmentNames`. See below. | | `environmentNames` | query | array | no | Optional environment names. Repeat the query key for multiple values, for example `environmentNames=Production&environmentNames=Staging`. Matches any listed name within the scoped workspace, case-insensitively. URL-encode names, for example `environmentNames=Pre%20Production`. Cannot be combined with `environmentIds`. See below. | #### Polling for what has changed `changedSince` is the field to build a reporter on. Only deployments whose `changedUtc` is strictly after it come back, so the same value never hands you the same row twice. **The sort direction flips with it, deliberately.** - **Without `changedSince`** you are browsing, and the most recent change comes first. - **With `changedSince`** you are catching up, and the *oldest* change comes first - so you walk forward through what happened and can stop anywhere with a coherent position behind you. The loop is: take the highest `changedUtc` in what you were given, hand it back as the next `changedSince`, repeat. Use the value from the response rather than your own clock; the timestamps are the server's, and your clock is not. Ordering is by when a deployment last changed rather than when it was created, because the column a page boundary sits on has to be the column the filter uses. That column is written when the row is created, refreshed while the deployment runs, and updated on every status transition. For rows written before every writer set it, `changedUtc` falls back to the creation date, so a deployment always has a place in the timeline. Keep `changedSince` on **every** request of the same walk. Dropping it midway flips the direction, and the cursor you are holding is then read the other way round. #### Paging Paging is by cursor, exactly as on [List releases](https://www.jawsdeploy.net/rest-api/releases-list). Read the first page without `cursor`, hand each response's `nextCursor` back as the next `cursor`, and keep going until it comes back `null`. A short page is not the last page - only a `null` cursor is. The token is opaque, and one that does not decode is refused with `400` rather than quietly restarting you at page one. #### Field casing This endpoint answers in lower camel case - `deploymentId`, `errorCount` - as every listing in this reference does. [Get deployment status](https://www.jawsdeploy.net/rest-api/deployments-status) is the exception: its `status` object and log entries are Pascal case (`Status`, `ErrorCount`). The two shapes describe the same deployment and cannot share a parser. #### Status values `status` is one of `Queued`, `Validating`, `AwaitingSlot`, `Running`, `Completed`, `Failed` or `Cancelled`. `Completed`, `Failed` and `Cancelled` are terminal - a deployment in one of them will not change again. The other four are transient, and a deployment can move between `AwaitingSlot` and `Validating` more than once before it runs. `Completed` means the engine reached the end of the step list, not that the deployed work was healthy. That is the separate question `errorCount` answers, and [Get deployment status](https://www.jawsdeploy.net/rest-api/deployments-status) covers both in full. #### Filtering by environment Keep exactly one scope parameter (`workspaceId`, `projectId` or `releaseId`). Environment filters also work with `projectId` plus `version` and with `changedSince`. They narrow that scope and do not replace it. Supply **either** `environmentIds` **or** `environmentNames`, repeating the query key once per value. Do not send a comma-separated list. A deployment matches **any** of the supplied environments. Both IDs and names are trimmed and compared case-insensitively, and duplicate values are ignored. Every value must resolve to a non-deleted environment in the scoped workspace. For a project or release scope, this is the workspace that owns the project. Use [List environments](https://www.jawsdeploy.net/rest-api/environments-list) to find IDs and names. Disabled environments are still valid filters, so you can read their deployment history. Any invalid value rejects the whole request with `400`. A valid filter with no matching deployments returns an empty `deployments` array. Filtering happens **before pagination**. When following `nextCursor`, keep the same scope, environment filters and `changedSince` value on every page. If you change the filters, start again without a cursor. ### Response fields | Field | Type | Description | |---|---|---| | `workspaceId` | string | Echoed back when you asked by workspace, otherwise `null`. | | `projectId` | string | Echoed back when you asked by project, otherwise `null`. | | `releaseId` | string | The release you asked by - or, for a `projectId` and `version` read, the release that version resolved to. `null` otherwise. That makes this the cheapest way to turn a version into a release id. | | `deployments` | object[] | One page of deployments. Empty array when nothing matches - including when a workspace holds no project you may see, which is not an error. | | `deployments[].deploymentId` | string | Pass it to [Get deployment status](https://www.jawsdeploy.net/rest-api/deployments-status) or [Cancel a deployment](https://www.jawsdeploy.net/rest-api/deployments-cancel). | | `deployments[].status` | string (enum) | `Queued`, `Validating`, `AwaitingSlot`, `Running`, `Completed`, `Failed` or `Cancelled`. See below. | | `deployments[].projectId` | string | The project the deployed release belongs to. | | `deployments[].projectName` | string | That project's name as it reads now. | | `deployments[].releaseId` | string | The release that was deployed. Read it with [Get a release](https://www.jawsdeploy.net/rest-api/releases-details). | | `deployments[].releaseVersion` | string | That release's version. | | `deployments[].environmentId` | string | The one environment this deployment targeted. | | `deployments[].environmentName` | string | That environment's name, or `null` when it no longer resolves. | | `deployments[].createdUtc` | string | When the deployment was created, ISO 8601 with offset. | | `deployments[].deploymentDateUtc` | string | When it is due to run. The same as `createdUtc` for an immediate deploy, later for one scheduled with `deploymentDateUnixMillis`. | | `deployments[].completeUtc` | string | When it finished, or `null` while it has not. | | `deployments[].changedUtc` | string | When it last changed - created, started, retried, finished or cancelled. **This is the field the list is ordered and filtered by**, and the one to feed back as `changedSince`. | | `deployments[].errorCount` | integer | Error-level log entries recorded so far. It only grows, and it counts log lines rather than failed steps. A `Completed` deployment can still have a non-zero count. | | `deployments[].warningCount` | integer | The same count for warning-level entries. | | `deployments[].automated` | boolean | `true` when a lifecycle started this deployment on its own, rather than a person or a pipeline asking for it. | | `deployments[].createdBy` | object | Who started it: `userId`, `name`, `email`. `null` for an automated deployment, or when the creator no longer resolves. | | `nextCursor` | string | Pass back as `cursor` to read the next page. `null` means this page was the last one. | ### Errors | Status | Meaning | |---|---| | 400 | None of `workspaceId`, `projectId` and `releaseId` was given, or more than one was - the response reads `give exactly one of workspaceId, projectId or releaseId`. | | 400 | `version` was sent without `projectId` - the response reads `version only applies together with projectId`. | | 400 | `changedSince` is not an ISO 8601 date and time. The message names the format it wanted, e.g. `2026-09-02T14:05:00Z`. | | 400 | `cursor` is not a token this endpoint issued - the response reads `invalid cursor`. | | 400 | Invalid `workspaceId`, `projectId` or `releaseId`, or no release with that `version` exists for the project. A resource in another organization is reported the same way as one that does not exist. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to view deployments of the project or release you named. A workspace read never fails this way - projects you cannot see are left out of the page instead. | | 400 | Both environment filters were supplied - `give only one of environmentIds or environmentNames`. | | 400 | An environment filter contains an empty or whitespace-only value - `environment filters must not contain blank values`. | | 400 | At least one environment is unknown, deleted, or outside the scoped workspace - `one or more environments are invalid for this workspace`. The whole request is rejected, even if other values match. Environments in another organization produce the same error. | Example request: ``` GET /api/deployment/list?projectId=prj_abc123&changedSince=2026-09-02T14:05:00Z&max=50 HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic // or every deployment of one release: GET /api/deployment/list?releaseId=rel_9f4c HTTP/1.1 // or everything in a workspace you can see: GET /api/deployment/list?workspaceId=ws_abc HTTP/1.1 // Project deployments in either environment, filtered before paging: GET /api/deployment/list?projectId=prj_abc123&environmentIds=env_prod&environmentIds=env_stage&max=50 HTTP/1.1 // One release, selected by project and version, in either named environment: GET /api/deployment/list?projectId=prj_abc123&version=2.5.3&environmentNames=Production&environmentNames=Pre%20Production HTTP/1.1 // Poll a workspace for changes in Production: GET /api/deployment/list?workspaceId=ws_abc&environmentNames=Production&changedSince=2026-09-02T14:05:00Z HTTP/1.1 // Filter a release directly by environment ID: GET /api/deployment/list?releaseId=rel_9f4c&environmentIds=env_prod HTTP/1.1 ``` Example response: ``` { "workspaceId": null, "projectId": "prj_abc123", "releaseId": null, "deployments": [ { "deploymentId": "dep_a1b2c3", "status": "Completed", "projectId": "prj_abc123", "projectName": "Acme Web", "releaseId": "rel_9f4c", "releaseVersion": "2.5.3", "environmentId": "env_prod", "environmentName": "Production", "createdUtc": "2026-09-05T09:20:01.004+00:00", "deploymentDateUtc": "2026-09-05T09:20:01.004+00:00", "completeUtc": "2026-09-05T09:24:47.881+00:00", "changedUtc": "2026-09-05T09:24:47.881+00:00", "errorCount": 0, "warningCount": 2, "automated": false, "createdBy": { "userId": "usr_ci", "name": "Build agent", "email": "ci@example.com" } } ], "nextCursor": "NjM4OTM5MzUwODgxMDAwMDAwfGRlcF9hMWIyYzM=" } ``` ## Get deployment status Source: https://www.jawsdeploy.net/rest-api/deployments-status | Section: Deployments Fetch live status and logs for a running or completed deployment. `GET /api/deployment` Returns the current status of a deployment, plus a chunk of logs. To poll incrementally, pass `status.LastLogDateTick` from the previous response as `getLogsAfter`. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `deploymentId` | query | string | yes | ID of the deployment. | | `skipLogs` | query | boolean | no | Set to `true` to skip the log body and just get status. | | `getLogsAfter` | query | integer | no | A .NET `DateTimeOffset.Ticks` value, normally `status.LastLogDateTick` from the previous response. Only log entries strictly newer than this value are returned. | #### The status object `status` describes the deployment as a whole and is returned whether or not you asked for logs. `Status`, `ErrorCount` and `WarningCount` are always present. Every other field can be `null`, and every date is UTC. - `Status` - how far the deployment engine got, as one of the names listed under Status values below. It reports the state of the run, not a verdict on the deployed work - see Deciding whether a deployment succeeded. - `ErrorCount` - how many error-level log entries the deployment has recorded so far, counting entries logged at `Error` and `Critical`. It only ever grows, and it counts log lines rather than failed steps, so one failing step can add several. A deployment that reached `Completed` can still have a non-zero `ErrorCount`. - `WarningCount` - the same count for entries logged at `Warning`. - `LastLogDate` and `LastLogDateTick` - the timestamp of the newest log entry of this deployment, as an ISO 8601 date and as the matching .NET tick count. Feed the tick value back as `getLogsAfter` to page through new entries. Both are `null` until the deployment writes its first log entry. - `LastUpdate` - when the server last updated the deployment record. A running deployment refreshes this as it makes progress, so it doubles as a liveness signal. - `LastAttemptDate` - when a worker last picked the deployment up. A deployment that waits for a slot and is retried has this set more than once. - `CancellationRequestedDate` - when cancellation was requested, or `null` if it was not. This can be set while the deployment is still `Running`, because the request is recorded first and the deployment stops once it reaches a point where it can. - `CancellationRequestedByName` - the email address of the user who requested the cancellation, falling back to their display name. - `CompleteDate` - when the deployment finished. `null` until it does. Each entry in `logs` carries its own `ErrorCount` and `WarningCount`. Those are not copies of the deployment totals - they count the failures recorded underneath that entry in the log tree, so a step group reports the errors of its children while the error line itself stays at `0`. #### Status values `Status` is one of seven names, returned exactly as spelled here. - `Queued` - the deployment has been accepted and is waiting for a worker. Every deployment starts here. - `Validating` - a worker has claimed the deployment and is preparing it to run. - `AwaitingSlot` - the deployment is ready but the organization has no free deployment slot, so it is waiting for one. It goes back to `Validating` when a slot frees up. - `Running` - the deployment is executing its steps. - `Completed` - the engine reached the end of the step list. It means the run finished, not that the deployed work was healthy. - `Failed` - the engine could not finish the run. The usual causes are an agent that could not be reached, a package or script module that could not be delivered to a target, a step that timed out, a deployment that outlived its maximum duration, or an unhandled error in the run itself. The reason is written to the log. - `Cancelled` - the deployment was stopped after a cancellation request. `Completed`, `Failed` and `Cancelled` are terminal - a deployment in one of them will not change again, so this is where polling should stop. The other four are transient, and a deployment can move between `AwaitingSlot` and `Validating` more than once before it runs. #### Deciding whether a deployment succeeded `Status` and `ErrorCount` answer two different questions. `Status` says whether the engine completed the orchestration. `ErrorCount` says whether the work it orchestrated reported problems. A script that exits with a non-zero code, and a PowerShell command that throws, do not fail their step and do not fail the deployment. The agent reports the step as completed, and the failure is recorded as a log entry - which is what moves `ErrorCount`. A deployment in which every script failed still returns `Status` of `Completed`. Treat a deployment as successful only when all three of these hold: 1. `Status` is `Completed`. 2. `ErrorCount` is `0`. 3. The run reached the targets you expected - see Steps that match no targets below. #### How errorAction changes the counts Every step carries an `errorAction` of `Stop` or `Continue`. It chooses the level a script failure is logged at, and nothing else: - `Stop` - the failure is logged at `Error`, so it increments `ErrorCount`. - `Continue` - the failure is logged at `Warning`, so it increments `WarningCount` and leaves `ErrorCount` untouched. Neither value ends the deployment. On a project whose steps use `Continue`, an `ErrorCount` of `0` no longer proves the run was clean, and `WarningCount` and the log have to be read as well. #### Later steps still run A step that records errors does not stand down the steps after it. The engine offers every remaining step, and only the `executeCondition` of that step can hold it back: - `Always` - the step runs whatever happened before it. - `AllPreviousStepsSucceeded` - the step is skipped when the error counts recorded against all preceding steps add up to more than zero. This is the only condition that reacts to earlier failures, and it counts the same error-level entries that drive `ErrorCount`, so an earlier step set to `Stop` is what arms it and `Continue` is what disarms it. - `VariableCheck` - the step runs only where the named boolean variable resolves to true. A skipped step is not a failure. It is recorded as skipped and adds nothing to `ErrorCount`. #### Steps that match no targets A step whose machine ID or tag filters match no agent has nothing to run on. That is not treated as an error: the step is recorded as finished, contributes no errors, and a deployment made entirely of such steps reaches `Completed` having done no work. A step whose only matching agents are disabled behaves the same way. Nothing in the `status` object distinguishes this from a step that ran everywhere. To tell them apart, read `logs`: every target a step ran on appears as a child entry named `Machine / ` beneath that step entry, and a step that matched nothing has no such children. Agents that were considered and filtered out are listed only when the deployment runs with the boolean project variable `__debug` set to true. ### Errors | Status | Meaning | |---|---| | 400 | Invalid or unknown `deploymentId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/deployment?deploymentId=dep_a1b2c3 HTTP/1.1 Authorization: Basic ``` Example response: ``` { "status": { "Status": "Running", "ErrorCount": 0, "WarningCount": 0, "LastLogDate": "2026-07-28T12:34:55+00:00", "LastLogDateTick": 639208388950000000, "LastUpdate": "2026-07-28T12:34:56+00:00", "CancellationRequestedDate": null, "CancellationRequestedByName": null, "LastAttemptDate": "2026-07-28T12:34:54+00:00", "CompleteDate": null }, "logs": [ { "CreatedLocalTime": null, "CreatedUtc": "2026-07-28T12:34:55+00:00", "CreatedUtcTick": 639208388950000000, "Data": "Step 1 starting", "DeploymentId": "dep_a1b2c3", "DeploymentMonitorId": null, "ErrorCount": 0, "WarningCount": 0, "ExceptionData": null, "Id": "log_123", "LogLevel": "Information", "ParentLogId": null, "StepId": "step_123", "ExecutionStatus": null, "GroupStatus": null, "Expanded": false, "ExpandLines": null } ] } ``` ## Cancel a deployment Source: https://www.jawsdeploy.net/rest-api/deployments-cancel | Section: Deployments Cancel a queued or running deployment. `POST /api/deployment/cancel` Cancels a deployment that is either queued or actively running. Once cancellation is accepted the deployment transitions to a cancelling/cancelled state asynchronously. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `deploymentId` | body | string | yes | ID of the deployment to cancel. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `deploymentId`, or the deployment is not in a `Running` or `Queued` state. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/deployment/cancel HTTP/1.1 Authorization: Basic Content-Type: application/json { "deploymentId": "dep_a1b2c3" } ``` Example response: ``` {} ``` ## Upload a package Source: https://www.jawsdeploy.net/rest-api/package-store-upload | Section: Package Store Push a build artifact into the workspace feed. `POST /api/packagestore/package` Uploads a package (nupkg / zip / tar / tar.gz) into the workspace's built-in feed. The package ID and version are parsed from the **filename** - see the [package types](https://www.jawsdeploy.net/rest-api/package-types) page. The request is `multipart/form-data` with two fields: - `WorkspaceId` - target workspace ID - `PackageFile` - the package file (max 1 GB) ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `WorkspaceId` | form | string | yes | ID of the destination workspace. | | `PackageFile` | form | file | yes | The package binary. Filename must be `..`. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `WorkspaceId`, missing `PackageFile`, or filename that doesn't match the `..` pattern. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/packagestore/package HTTP/1.1 Authorization: Basic Content-Type: multipart/form-data; boundary=----X ------X Content-Disposition: form-data; name="WorkspaceId" ws_abc ------X Content-Disposition: form-data; name="PackageFile"; filename="Acme.Web.2.5.3.zip" Content-Type: application/octet-stream ------X-- ``` Example response: ``` {} ``` ## Negotiate a direct upload Source: https://www.jawsdeploy.net/rest-api/package-store-negotiate-upload | Section: Package Store Request a pre-authorized direct-to-storage upload URL for a package. `POST /api/packagestore/package/negotiate` Starts a **direct upload**: for a workspace whose package store is in a region, the server returns a short-lived, write-only URL that points straight at that region's blob storage, so your CI uploads the bytes without routing them through the Jaws web app. For every other workspace the server returns mode `legacy`, and you fall back to the classic [multipart upload](https://www.jawsdeploy.net/rest-api/package-store-upload). A direct upload is three plain HTTPS calls, no SDK required: 1. `POST /api/packagestore/package/negotiate` (this endpoint) with the workspace and file name. If the response `mode` is `direct`, take the returned `uploadUrl`. 2. `PUT` the raw file bytes to that `uploadUrl`, adding the header `x-ms-blob-type: BlockBlob` (the one Azure-ism). The blob lands in a private pending/quarantine path and is not part of the feed yet. 3. `POST /api/packagestore/package/confirm` to validate and promote it - see [Confirm a direct upload](https://www.jawsdeploy.net/rest-api/package-store-confirm-upload). The upload URL is scoped to a single blob, is write-only, and expires after 4 hours. It is never logged or stored. Nothing is downloadable until you call confirm. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `WorkspaceId` | body | string | yes | ID of the destination workspace. | | `FileName` | body | string | yes | Package file name including ID, version and extension, e.g. `Acme.Web.2.5.3.zip`. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `WorkspaceId`, or a `FileName` that doesn't match the `..` pattern. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to push packages to this workspace. | Example request: ``` POST /api/packagestore/package/negotiate HTTP/1.1 Authorization: Basic Content-Type: application/json { "WorkspaceId": "ws_abc", "FileName": "Acme.Web.2.5.3.zip" } ``` Example response: ``` // mode "direct" - upload straight to regional storage { "mode": "direct", "uploadUrl": "https://jawsfeedsau.blob.core.windows.net/pending/...&sig=..." } // mode "legacy" - use the multipart upload endpoint instead { "mode": "legacy" } ``` ## Confirm a direct upload Source: https://www.jawsdeploy.net/rest-api/package-store-confirm-upload | Section: Package Store Promote a directly uploaded package into the workspace feed. `POST /api/packagestore/package/confirm` Completes the [direct upload](https://www.jawsdeploy.net/rest-api/package-store-negotiate-upload) flow. Call this after you have `PUT` the file bytes to the negotiated `uploadUrl`. The server validates the pending blob (size limit and file name), promotes it out of the quarantine path into the workspace's feed container, and registers the package so it becomes available to releases and deployments. Until confirm succeeds, the uploaded bytes are not part of the feed and cannot be downloaded. Send the same `WorkspaceId` and `FileName` you passed to negotiate. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `WorkspaceId` | body | string | yes | ID of the destination workspace. | | `FileName` | body | string | yes | The same package file name used in the negotiate call. | ### Errors | Status | Meaning | |---|---| | 400 | The pending blob is missing, exceeds the size limit, or the `WorkspaceId` / `FileName` is invalid. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to push packages to this workspace. | Example request: ``` POST /api/packagestore/package/confirm HTTP/1.1 Authorization: Basic Content-Type: application/json { "WorkspaceId": "ws_abc", "FileName": "Acme.Web.2.5.3.zip" } ``` Example response: ``` {} ``` ## List workspaces Source: https://www.jawsdeploy.net/rest-api/workspaces-list | Section: Workspaces List the workspaces visible to the service account. `GET /api/workspace` Lists every workspace in the organization that the authenticated service account is allowed to manage. Each workspace reports its **package store location** as `regionId` and `regionName`; both are `null` for workspaces that use the default (non-regional) package store. ### Errors | Status | Meaning | |---|---| | 401 | Missing or invalid Basic auth credentials. | Example request: ``` GET /api/workspace HTTP/1.1 Authorization: Basic ``` Example response: ``` { "workspaces": [ { "workspaceId": "ws_abc", "name": "Main", "slug": "main", "description": null, "regionId": null, "regionName": null }, { "workspaceId": "ws_syd", "name": "Sydney", "slug": "sydney", "description": "AU workloads", "regionId": "au", "regionName": "Australia" } ] } ``` ## Create a workspace Source: https://www.jawsdeploy.net/rest-api/workspaces-create | Section: Workspaces Create a workspace, optionally pinned to a package store location. `POST /api/workspace` Creates a workspace in the organization the service account belongs to. Pass `regionId` to choose the **package store location** - the region whose blob storage holds this workspace's Jaws-feed packages (list the options with [List package store locations](https://www.jawsdeploy.net/rest-api/workspaces-list-regions)). Omit it (or send `null`) to use the default location, which keeps the classic behaviour. The package store location is **fixed at creation and cannot be changed afterwards** - stored packages physically live in that region's storage account, so moving a workspace would mean copying every package across regions. Choose based on where the workspace's **deployment targets** live, not where the operator sits. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `name` | body | string | yes | Display name of the workspace. | | `slug` | body | string | no | URL-friendly identifier; generated from the name when omitted. | | `description` | body | string | no | Optional free-text description. | | `regionId` | body | string | no | Package store location ID. Omit/null for the default location. Fixed at creation. | | `variableSyntaxMode` | body | string | no | Variable substitution syntax: `Legacy` preserves plain-token behavior; `Extended` enables filters and conditionals. Omit/null to use `Extended`. | ### Errors | Status | Meaning | |---|---| | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to add workspaces in this organization. | | 422 | Validation failed - e.g. a missing name, a duplicate slug, or a `regionId` that is not an active package store location. | Example request: ``` POST /api/workspace HTTP/1.1 Authorization: Basic Content-Type: application/json { "name": "Sydney", "slug": "sydney", "description": "AU workloads", "regionId": "au", "variableSyntaxMode": "Extended" } ``` Example response: ``` { "workspaceId": "ws_syd", "name": "Sydney", "slug": "sydney", "regionId": "au", "regionName": "Australia", "variableSyntaxMode": "Extended" } ``` ## List package store locations Source: https://www.jawsdeploy.net/rest-api/workspaces-list-regions | Section: Workspaces List the active regions a workspace's package store can be pinned to. `GET /api/workspace/regions` Lists the active **package store locations** (regions) available for new workspaces. Use the returned `regionId` as the `regionId` when you [create a workspace](https://www.jawsdeploy.net/rest-api/workspaces-create). The list is environment-specific: an installation with no extra regions configured returns an empty list, in which case every workspace uses the default location. ### Errors | Status | Meaning | |---|---| | 401 | Missing or invalid Basic auth credentials. | Example request: ``` GET /api/workspace/regions HTTP/1.1 Authorization: Basic ``` Example response: ``` { "regions": [ { "regionId": "eu", "name": "Europe" }, { "regionId": "au", "name": "Australia" } ] } ``` ## List projects Source: https://www.jawsdeploy.net/rest-api/projects-list | Section: Projects List the projects visible to the service account in a workspace. `GET /api/project` Returns every project the authenticated service account can see in the given workspace. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace to list projects in. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/project?workspaceId=ws_abc HTTP/1.1 Authorization: Basic ``` Example response: ``` { "workspaceId": "ws_abc", "projects": [ { "projectId": "prj_abc123", "name": "Acme web", "description": "Main site" } ] } ``` ## Create a project Source: https://www.jawsdeploy.net/rest-api/projects-create | Section: Projects Create a new project in a workspace. `POST /api/project` Creates a new project. The caller must have project-edit permission in the target workspace. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Project name. | | `description` | body | string | no | Optional description. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project { "workspaceId": "ws_abc", "name": "Acme web", "description": "Main site" } ``` Example response: ``` { "projectId": "prj_abc123" } ``` ## Clone a project Source: https://www.jawsdeploy.net/rest-api/projects-clone | Section: Projects Clone an existing project into the same workspace. `POST /api/project/clone` Creates a copy of an existing project (steps, channels, settings) inside the same workspace. The optional `targetFolderId` controls placement. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `sourceProjectId` | body | string | yes | ID of the project to clone. | | `targetFolderId` | body | string | no | Folder to place the clone in. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `sourceProjectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/clone { "sourceProjectId": "prj_abc123" } ``` Example response: ``` { "projectId": "prj_clone456" } ``` ## Update a project Source: https://www.jawsdeploy.net/rest-api/projects-update | Section: Projects Update project name and description. `PUT /api/project` Updates a project's general settings. Pass only the fields you want to change. The same call also sets the project's two deployment timeout overrides, which carry partial-update semantics of their own - see below. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `maxDeploymentDurationSeconds` | body | integer | no | Wall-clock cap on a whole deployment of this project, in seconds. `0` removes the override and restores the system default. See below. | | `stepIdleMaxSeconds` | body | integer | no | How long one step may go without the agent reporting progress before it is interrupted, in seconds. `0` removes the override and restores the system default. See below. | #### Deployment limits Two optional per-project overrides of timeouts that otherwise come from server configuration. Both are in **seconds**. - `maxDeploymentDurationSeconds` - a wall-clock cap on the whole deployment, whether or not it is making progress. This is what stops a hung deployment holding one of the organization's parallel deployment slots indefinitely. - `stepIdleMaxSeconds` - how long a single step may go without the agent reporting progress before it is interrupted. The interruption is written to the deployment log, naming the timeout that was applied. Each takes three kinds of value, so one limit can be set without restating the other: - **omitted**, or `null` - leave whatever is stored alone. - `0` - remove this project's override, so the configured default applies again. - **a positive number** - use this value for this project. There is deliberately no value meaning *never apply this limit*. For the duration cap that would reinstate exactly the hang it exists to prevent, so a project that genuinely needs longer sets a longer number instead. The ceiling is **2000000 seconds**, a little under 24 days. Past that the runner cannot arm its own timer, and only the scheduler's backstop would stop the deployment - which marks the row failed and frees the slot without being able to stop the work already running. A larger value is refused up front rather than accepted and quietly not honoured. Both limits are validated **before anything is written**, and the general settings and the limits are saved in one transaction. A request that renames the project and carries an invalid limit therefore changes nothing at all - it does not land the rename and then fail. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | | 400 | A deployment limit is negative - the response reads `deployment limits cannot be negative - use 0 to reset a limit to the system default`. Nothing is changed. | | 400 | A deployment limit is above `2000000` seconds - the message names the ceiling and why it exists. Nothing is changed. | Example request: ``` PUT /api/project HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic Content-Type: application/json { "projectId": "prj_abc123", "name": "Acme web (renamed)", "maxDeploymentDurationSeconds": 3600, "stepIdleMaxSeconds": 600 } ``` Example response: ``` {} ``` ## Delete a project Source: https://www.jawsdeploy.net/rest-api/projects-delete | Section: Projects Soft-delete a project. `DELETE /api/project` Soft-deletes the project. The project keeps its history but stops accepting new releases and deployments. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project to delete. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/project?projectId=prj_abc123 HTTP/1.1 ``` Example response: ``` {} ``` ## List script module assignments Source: https://www.jawsdeploy.net/rest-api/projects-script-modules-list | Section: Projects See which script modules are imported into a project. `GET /api/project/script-modules` Returns every workspace script module with an `imported` flag indicating whether the project currently imports it. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/project/script-modules?projectId=prj_abc123 ``` Example response: ``` { "projectId": "prj_abc123", "scriptModules": [ { "scriptModuleId": "...", "name": "...", "language": "powershell", "imported": true } ] } ``` ## Replace script module assignments Source: https://www.jawsdeploy.net/rest-api/projects-script-modules-replace | Section: Projects Set the full list of script modules imported by a project. `PUT /api/project/script-modules` Replaces the project's script module assignments. Modules not present in `scriptModuleIds` get unimported; new ones get imported. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `scriptModuleIds` | body | array | no | IDs of every script module the project should import. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/project/script-modules { "projectId": "prj_abc123", "scriptModuleIds": ["sm_1", "sm_2"] } ``` Example response: ``` {} ``` ## Import one script module Source: https://www.jawsdeploy.net/rest-api/projects-script-module-assign | Section: Projects Import a single workspace script module into a project. `POST /api/project/script-module` Adds one script module assignment without affecting the others. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `scriptModuleId` | body | string | yes | ID of the script module to import. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or `scriptModuleId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/script-module { "projectId": "prj_abc123", "scriptModuleId": "sm_1" } ``` Example response: ``` {} ``` ## Unimport one script module Source: https://www.jawsdeploy.net/rest-api/projects-script-module-unassign | Section: Projects Remove a single script module assignment from a project. `DELETE /api/project/script-module` Removes one script module assignment. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project. | | `scriptModuleId` | query | string | yes | ID of the script module to unimport. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or `scriptModuleId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/project/script-module?projectId=prj_abc123&scriptModuleId=sm_1 ``` Example response: ``` {} ``` ## List project steps Source: https://www.jawsdeploy.net/rest-api/projects-steps-list | Section: Projects List the ordered deployment steps for a project. `GET /api/project/steps` Returns the project's deployment plan: every step in order, with its template, scoping, run mode, and properties. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project. | #### Reading `errorAction` `errorAction` is `Stop` or `Continue`. It sets the level a failing script is logged at - `Error` for `Stop`, `Warning` for `Continue` - and does not stop the deployment either way. [Update a project step](https://www.jawsdeploy.net/rest-api/project-step-update) covers it, and `executeCondition`, in full. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/project/steps?projectId=prj_abc123 ``` Example response: ``` { "projectId": "prj_abc123", "steps": [ { "projectStepId": "...", "name": "Deploy Acme.Web", "order": 1, "runOn": "TargetMachine", "environments": ["env_a"], "errorAction": "Stop", "disabled": false, "properties": "[...]" } ] } ``` ## List project channels Source: https://www.jawsdeploy.net/rest-api/project-channels-list | Section: Project Channels List the channels (release tracks) defined on a project, including their version rules. `GET /api/project/channels` Returns every channel for a project: name, description, attached lifecycle, default flag, and the channel's version rules. #### How the rules are evaluated A channel gates two things, and each is optional - a blank field means *no constraint*. - The **release version rule** (`versionRange`, `versionTagRegex`) gates which release versions may be created in the channel. - The **package rules** (`packageRules`) gate which package versions may be pinned into those releases. `versionRange` bounds the version **number** and takes no account of the prerelease tag; `versionTagRegex` decides whether tagged versions are allowed at all. The endpoints of a range therefore compare on the number alone, so `[2.0,4.0)` accepts `2.0.0-beta` and rejects `4.0.0-beta`. This differs from raw NuGet range semantics, which sort a prerelease below its own release. `versionTagRegex` is matched against the prerelease label **without** the leading `-`, and against an empty string for a stable version. So `^$` accepts stable versions only, `^.+$` accepts prereleases only, and `^beta.*$` accepts `2.1.0-beta3` but not `2.1.0` or `2.1.0-rc1`. The app's channel editor shows `^$` as *Stable only* and `^.+$` as *Prereleases only*; any other expression shows as a custom pattern. There is no separate mode field to set. `packageRules` are evaluated **in order and the first rule whose `packageFilter` matches a package wins**; a package matched by no rule is unconstrained. Each filter may only be used once per channel. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/project/channels?projectId=prj_abc123 Authorization: Basic ``` Example response: ``` { "projectId": "prj_abc123", "channels": [ { "channelId": "ch_1", "name": "Stable", "description": "Production releases", "lifecycleId": "lc_default", "isDefault": true, "versionRange": null, "versionTagRegex": "^$", "versionDefaultTag": null, "packageRules": [] }, { "channelId": "ch_2", "name": "Beta", "description": null, "lifecycleId": "lc_preview", "isDefault": false, "versionRange": "[2.0,4.0)", "versionTagRegex": "^beta.*$", "versionDefaultTag": "beta", "packageRules": [ { "packageFilter": "MyApp.Frontend", "versionRange": "[2.0,3.0)", "versionTagRegex": null }, { "packageFilter": "*", "versionRange": null, "versionTagRegex": "^$" } ] } ] } ``` ## Create a project channel Source: https://www.jawsdeploy.net/rest-api/project-channel-create | Section: Project Channels Create a new channel for a project, optionally gating which release and package versions it accepts. `POST /api/project/channel` Creates a new channel. If `isDefault` is true the previous default is cleared. All rule fields are optional; omit them all to create an unrestricted channel. #### How the rules are evaluated A channel gates two things, and each is optional - a blank field means *no constraint*. - The **release version rule** (`versionRange`, `versionTagRegex`) gates which release versions may be created in the channel. - The **package rules** (`packageRules`) gate which package versions may be pinned into those releases. `versionRange` bounds the version **number** and takes no account of the prerelease tag; `versionTagRegex` decides whether tagged versions are allowed at all. The endpoints of a range therefore compare on the number alone, so `[2.0,4.0)` accepts `2.0.0-beta` and rejects `4.0.0-beta`. This differs from raw NuGet range semantics, which sort a prerelease below its own release. `versionTagRegex` is matched against the prerelease label **without** the leading `-`, and against an empty string for a stable version. So `^$` accepts stable versions only, `^.+$` accepts prereleases only, and `^beta.*$` accepts `2.1.0-beta3` but not `2.1.0` or `2.1.0-rc1`. The app's channel editor shows `^$` as *Stable only* and `^.+$` as *Prereleases only*; any other expression shows as a custom pattern. There is no separate mode field to set. `packageRules` are evaluated **in order and the first rule whose `packageFilter` matches a package wins**; a package matched by no rule is unconstrained. Each filter may only be used once per channel. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `name` | body | string | yes | Channel name. Must be unique within the project. Max 200 characters. | | `description` | body | string | no | Optional description. | | `lifecycleId` | body | string | no | Lifecycle to bind to this channel. Must exist in the project's workspace. | | `isDefault` | body | boolean | no | Mark as the project's default channel. | | `versionRange` | body | string | no | Version range a release version must satisfy, e.g. `[2.0,3.0)`. Bounds the version number only. Blank for any version. | | `versionTagRegex` | body | string | no | Regular expression the release version's prerelease tag must match, e.g. `^beta.*$`. Use `^$` for stable versions only, `^.+$` for prereleases only. Blank for any tag. | | `versionDefaultTag` | body | string | no | Prerelease tag appended to versions auto-suggested for this channel, e.g. `beta`. Must itself match `versionTagRegex`. Blank to suggest plain version numbers. | | `packageRules` | body | array | no | Ordered list of package version rules. Evaluated in array order; the first matching `packageFilter` wins. | | `packageRules[].packageFilter` | body | string | yes | Package ID glob, e.g. `*` or `MyApp.*`. Case insensitive; `*` and `?` are the only wildcards and every other character is literal. Required on each rule - use `*` to match all packages. Max 500 characters. | | `packageRules[].versionRange` | body | string | no | Version range the package version must satisfy, e.g. `[2.0,3.0)`. Blank for any version. | | `packageRules[].versionTagRegex` | body | string | no | Regular expression the package version's prerelease tag must match, e.g. `^$` for stable versions only. Blank for any tag. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`, or a duplicate channel name. | | 400 | Validation failure. Rule fields are validated on save: an unparseable `versionRange`, an invalid `versionTagRegex` (backreferences and lookarounds are rejected), a `versionDefaultTag` which its own `versionTagRegex` would not match, a blank `packageFilter`, or two package rules sharing the same filter. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/channel Authorization: Basic Content-Type: application/json { "projectId": "prj_abc123", "name": "Beta", "lifecycleId": "lc_preview", "versionTagRegex": "^beta.*$", "versionDefaultTag": "beta", "packageRules": [ { "packageFilter": "MyApp.Frontend", "versionRange": "[2.0,3.0)" }, { "packageFilter": "*", "versionTagRegex": "^$" } ] } ``` Example response: ``` { "channelId": "ch_2" } ``` ## Update a project channel Source: https://www.jawsdeploy.net/rest-api/project-channel-update | Section: Project Channels Update a channel's name, description, lifecycle binding, default flag, or version rules. `PUT /api/project/channel` Pass only the fields you want to change. Setting `isDefault = false` on the current default makes the project have no default channel until another one is promoted. **Omitting a field leaves it unchanged; sending an empty value clears it.** That distinction applies to every rule field: omit `versionRange` to leave the stored range alone, or send `""` to remove the constraint. Likewise omit `packageRules` to leave the existing rules untouched, or send `[]` to remove all of them. When you do send `packageRules`, the list **replaces** the stored rules wholesale - there is no partial update of a single rule. #### How the rules are evaluated A channel gates two things, and each is optional - a blank field means *no constraint*. - The **release version rule** (`versionRange`, `versionTagRegex`) gates which release versions may be created in the channel. - The **package rules** (`packageRules`) gate which package versions may be pinned into those releases. `versionRange` bounds the version **number** and takes no account of the prerelease tag; `versionTagRegex` decides whether tagged versions are allowed at all. The endpoints of a range therefore compare on the number alone, so `[2.0,4.0)` accepts `2.0.0-beta` and rejects `4.0.0-beta`. This differs from raw NuGet range semantics, which sort a prerelease below its own release. `versionTagRegex` is matched against the prerelease label **without** the leading `-`, and against an empty string for a stable version. So `^$` accepts stable versions only, `^.+$` accepts prereleases only, and `^beta.*$` accepts `2.1.0-beta3` but not `2.1.0` or `2.1.0-rc1`. The app's channel editor shows `^$` as *Stable only* and `^.+$` as *Prereleases only*; any other expression shows as a custom pattern. There is no separate mode field to set. `packageRules` are evaluated **in order and the first rule whose `packageFilter` matches a package wins**; a package matched by no rule is unconstrained. Each filter may only be used once per channel. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `channelId` | body | string | yes | ID of the channel. | | `name` | body | string | no | New name. Must stay unique within the project. | | `description` | body | string | no | New description. | | `lifecycleId` | body | string | no | Lifecycle to bind. | | `isDefault` | body | boolean | no | Promote to default channel. | | `versionRange` | body | string | no | Version range a release version must satisfy. Omit to leave unchanged, send `""` to clear. | | `versionTagRegex` | body | string | no | Regular expression the release version's prerelease tag must match. Omit to leave unchanged, send `""` to clear. | | `versionDefaultTag` | body | string | no | Prerelease tag appended to auto-suggested versions. Omit to leave unchanged, send `""` to clear. | | `packageRules` | body | array | no | Replaces the channel's package rules wholesale. Omit to leave them unchanged, send `[]` to remove all of them. Element fields are the same as on create. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `channelId`, or a duplicate channel name. | | 400 | Validation failure. Rule fields are validated on save: an unparseable `versionRange`, an invalid `versionTagRegex` (backreferences and lookarounds are rejected), a `versionDefaultTag` which its own `versionTagRegex` would not match, a blank `packageFilter`, or two package rules sharing the same filter. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/project/channel Authorization: Basic Content-Type: application/json { "channelId": "ch_2", "versionRange": "[3.0,4.0)", "packageRules": [ { "packageFilter": "*", "versionTagRegex": "^$" } ] } ``` Example response: ``` {} ``` ## Delete a project channel Source: https://www.jawsdeploy.net/rest-api/project-channel-delete | Section: Project Channels Delete a project channel. `DELETE /api/project/channel` Deletes a project channel. Existing releases on the channel are not affected. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `channelId` | query | string | yes | ID of the channel. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `channelId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/project/channel?channelId=ch_2 ``` Example response: ``` {} ``` ## List imported workspace variables Source: https://www.jawsdeploy.net/rest-api/project-workspace-variables-list | Section: Project Variables List workspace-level variables currently imported into a project. `GET /api/project/workspace-variables` Returns every workspace variable that the project currently imports, including the bound `workspaceVariableId` and the local `projectVariableId`. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/project/workspace-variables?projectId=prj_abc123 ``` Example response: ``` { "projectId": "prj_abc123", "workspaceVariables": [ { "projectVariableId": "...", "workspaceVariableId": "...", "name": "ConnectionString", "variableType": "Text" } ] } ``` ## Replace imported workspace variables Source: https://www.jawsdeploy.net/rest-api/project-workspace-variables-replace | Section: Project Variables Set the full list of workspace variables imported into a project. `PUT /api/project/workspace-variables` Replaces the project's workspace variable imports. Variables not in `workspaceVariableIds` are unimported. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `workspaceVariableIds` | body | array | no | Workspace variable IDs to import. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or one of the workspace variable IDs not visible to the project. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/project/workspace-variables { "projectId": "prj_abc123", "workspaceVariableIds": ["wv_1", "wv_2"] } ``` Example response: ``` {} ``` ## Import one workspace variable Source: https://www.jawsdeploy.net/rest-api/project-workspace-variable-import | Section: Project Variables Import a single workspace variable into a project. `POST /api/project/workspace-variable` Imports a single workspace variable. Returns the project-side variable ID, even if the variable was already imported. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `workspaceVariableId` | body | string | yes | ID of the workspace variable. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or `workspaceVariableId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/workspace-variable { "projectId": "prj_abc123", "workspaceVariableId": "wv_1" } ``` Example response: ``` { "projectVariableId": "v_local1" } ``` ## Unimport one workspace variable Source: https://www.jawsdeploy.net/rest-api/project-workspace-variable-unimport | Section: Project Variables Remove a workspace variable import from a project. `DELETE /api/project/workspace-variable` Removes the workspace variable import from this project. The workspace variable itself is not affected. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | ID of the project. | | `workspaceVariableId` | query | string | yes | ID of the workspace variable to unimport. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or workspace variable not currently imported. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/project/workspace-variable?projectId=prj_abc123&workspaceVariableId=wv_1 ``` Example response: ``` {} ``` ## Create a project variable Source: https://www.jawsdeploy.net/rest-api/project-variable-create | Section: Project Variables Create a project-scoped variable. `POST /api/project/variable` Creates a variable that lives only on this project. Use `variableType = Text` for plain text and `Secret` for secrets. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `name` | body | string | yes | Variable name. | | `description` | body | string | no | Optional description. | | `variableType` | body | string | no | See below. | #### Variable types `variableType` accepts `Text`, `Secret`, `Script`, `Number`, `Boolean`, `Date`, `Map`, and `Json`. `Text` is used when the field is omitted. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId`, invalid `variableType`, or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/variable { "projectId": "prj_abc123", "name": "FeatureFlag.Beta", "variableType": "Text" } ``` Example response: ``` { "variableId": "v_xyz" } ``` ## Update a project variable Source: https://www.jawsdeploy.net/rest-api/project-variable-update | Section: Project Variables Update a project variable's name, description, or type. `PUT /api/project/variable` Updates the variable definition (not its values). Pass only the fields to change. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableId` | body | string | yes | ID of the variable. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `variableType` | body | string | no | See below. | #### Variable types `variableType` accepts `Text`, `Secret`, `Script`, `Number`, `Boolean`, `Date`, `Map`, and `Json`. Omit the field to leave the type unchanged. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableId` (e.g. it's an imported workspace variable, not a project-local one) or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/project/variable { "variableId": "v_xyz", "variableType": "Secret" } ``` Example response: ``` {} ``` ## Add a project variable value Source: https://www.jawsdeploy.net/rest-api/project-variable-value-create | Section: Project Variables Add a scoped value for a project variable. `POST /api/project/variable/value` Adds a new value for a project variable. Use `environmentsFilter`, `machineFilter`, `cloudTargetFilter`, and `tagFilter` to scope the value to a subset of deployment targets. Multiple values can exist for the same variable with different scopes. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableId` | body | string | yes | ID of the project variable. | | `theValue` | body | string | no | Value content. Optional - defaults to empty. | | `environmentsFilter` | body | array | no | Restrict to these environment IDs. | | `machineFilter` | body | array | no | Restrict to these machine IDs. | | `cloudTargetFilter` | body | array | no | Restrict to these cloud target IDs. | | `stepFilter` | body | array | no | Restrict this value to these project step IDs. IDs must belong to the variable's project. | | `tagFilter` | body | array | no | List of `{ tagIds: [...] }` sets. A target matches if it has every tag in at least one set. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableId`, or one of the filter IDs is not visible to the project's workspace. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/variable/value { "variableId": "v_xyz", "theValue": "true", "environmentsFilter": ["env_prod"] } ``` Example response: ``` { "variableValueId": "vv_1" } ``` ## Update a project variable value Source: https://www.jawsdeploy.net/rest-api/project-variable-value-update | Section: Project Variables Update a scoped value on a project variable. `PUT /api/project/variable/value` Updates a project variable value. Pass only the fields to change. Existing filters are preserved unless explicitly overwritten. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableValueId` | body | string | yes | ID of the variable value. | | `theValue` | body | string | no | New value content. | | `environmentsFilter` | body | array | no | Replacement environments filter. | | `machineFilter` | body | array | no | Replacement machine filter. | | `cloudTargetFilter` | body | array | no | Replacement cloud target filter. | | `stepFilter` | body | array | no | Replacement project step IDs. Omit to keep the existing step scope; pass an empty array to clear it. | | `tagFilter` | body | array | no | Replacement tag filter. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableValueId` or filter validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/project/variable/value { "variableValueId": "vv_1", "theValue": "false" } ``` Example response: ``` {} ``` ## Create a project step Source: https://www.jawsdeploy.net/rest-api/project-step-create | Section: Project Steps Append a new step to a project's deployment plan. `POST /api/project/step` Creates a step at the end of the project's step list. Subsequent calls (e.g. `PUT /api/project/step`) can fill in the step's properties. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `name` | body | string | yes | Step name. | | `stepTemplateId` | body | string | yes | ID of the workspace step template to use. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or `stepTemplateId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/step { "projectId": "prj_abc123", "name": "Deploy Acme.Web", "stepTemplateId": "st_1" } ``` Example response: ``` { "projectStepId": "ps_1" } ``` ## Clone a project step Source: https://www.jawsdeploy.net/rest-api/project-step-clone | Section: Project Steps Copy an existing step into the same or another project. `POST /api/project/step/clone` Copies a step from one project to another (or duplicates within the same project). The cloned step appears at the end of the target project's step list. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `sourceProjectStepId` | body | string | yes | ID of the source step. | | `targetProjectId` | body | string | yes | ID of the target project. | | `name` | body | string | no | Override name for the cloned step. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid source or target. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/project/step/clone { "sourceProjectStepId": "ps_1", "targetProjectId": "prj_def456" } ``` Example response: ``` { "projectStepId": "ps_2" } ``` ## Update a project step Source: https://www.jawsdeploy.net/rest-api/project-step-update | Section: Project Steps Update step name, scope, run mode, parallelism, error handling, and properties. `PUT /api/project/step` Updates a step on a project. Pass only the fields to change. `propertiesJson` is a JSON-encoded string matching the step template's property schema. Filters (`machineIdFilter`, `machineTagFilter`, `cloudTargetTagFilter`) constrain which targets the step runs on within the step's selected `environments`. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectStepId` | body | string | yes | ID of the step. | | `name` | body | string | no | New step name. | | `description` | body | string | no | New description. | | `stepTemplateId` | body | string | no | Move the step onto a step template as it stands now - another template's id to switch it, or the step's own template id to pull a newer version of it in. See below. | | `runOn` | body | string | no | See below. | | `errorAction` | body | string | no | See below. | | `disabled` | body | boolean | no | Disable the step without removing it. | | `executeCondition` | body | string | no | See below. | | `executeConditionScript` | body | string | no | Required when `executeCondition` is `VariableCheck`: the name of the boolean variable that must resolve to true. Despite the name, it is a variable name and not a script. | | `parallelMachines` | body | integer | no | Max machines to run on in parallel. | | `parallelCloudTargets` | body | integer | no | Max cloud targets to run on in parallel. | | `workerTagId` | body | string | no | Tag identifying the worker pool. | | `environments` | body | array | no | Environment IDs the step is restricted to. | | `machineIdFilter` | body | array | no | Restrict to these machine IDs. | | `machineTagFilter` | body | array | no | Tag-set filter for machines. | | `cloudTargetTagFilter` | body | array | no | Tag-set filter for cloud targets. | | `propertiesJson` | body | string | no | JSON-encoded step property values. | | `onStepFailure` | body | string | no | What a failed step does to the rest of the deployment. `ContinueToNextStep` (default) or `StopDeployment`. See below. | | `runAfterStop` | body | boolean | no | Run this step even when an earlier step stopped the deployment. Defaults to `false`. See below. | | `machineOrder` | body | string | no | The order the step works through an environment's machines. `MachineName` (default) or `TagPriority`. Parsed case-insensitively. See below. | | `machineOrderTagIds` | body | array | no | Tag IDs in priority order, highest first, used when `machineOrder` is `TagPriority`. **Only stored while the order uses it** - saving with `MachineName` order drops the list. See below. | | `onMachineFailure` | body | string | no | What one machine failing does to the rest of the step. `ContinueToOtherMachines` (default) or `StopStep`. Neither ends the deployment - that is `onStepFailure`. See below. | | `waitBetweenMachineGroups` | body | boolean | no | Finish every machine of one tag rank before the next rank starts. Defaults to `false`. See below. | #### Run mode `runOn` accepts `TargetMachine`, `Worker`, or `WorkerToCloudTargets`. The value must also be included in the selected step template's `supportedRunModes`; otherwise the request returns `400`. #### Error action `errorAction` accepts `Stop` or `Continue`. It chooses the level a script failure is logged at, and nothing else - neither value stops the deployment. - `Stop` - a failing script is logged at `Error`, so it counts towards the deployment `ErrorCount`. - `Continue` - a failing script is logged at `Warning`, so it counts towards `WarningCount` instead. A script that exits with a non-zero code does not fail its step and does not fail the deployment either way. See [Get deployment status](https://www.jawsdeploy.net/rest-api/deployments-status) for how to tell a clean run from a merely finished one. #### Execute condition `executeCondition` accepts `Always`, `AllPreviousStepsSucceeded`, or `VariableCheck`. It is the only setting that can hold a step back because of what happened earlier in the deployment. - `Always` - the step runs whatever came before it. - `AllPreviousStepsSucceeded` - the step is skipped when the error counts recorded against the preceding steps add up to more than zero. Only steps whose `errorAction` is `Stop` can contribute to that sum, and the first step in a project has nothing before it, so the condition never holds it back. - `VariableCheck` - the step runs only on the targets where the boolean variable named in `executeConditionScript` resolves to true. A step held back by its condition is recorded as skipped, not failed, and adds nothing to `ErrorCount`. #### Stopping the deployment `onStepFailure` accepts `ContinueToNextStep` or `StopDeployment` and defaults to `ContinueToNextStep`. This is the setting that can end a run. `errorAction`, above, is not. - `ContinueToNextStep` - the historical behaviour. The deployment can no longer report success, but every later step is still offered, and only its own `executeCondition` holds it back. - `StopDeployment` - no later step runs, apart from any step whose `runAfterStop` is `true`. Those still run, so a process that has to announce its own failure has somewhere to do it from. The rest are recorded as skipped, with the reason logged against them. A step counts as failed here when its status is `Failed` or `TimedOut`, or when it recorded any errors at all. That is where `errorAction` comes back in. A failing script is counted as an error only when the step's `errorAction` is `Stop`, so a step left on `Continue` will not stop the deployment when its script fails, whatever `onStepFailure` says. Set the two together. Values are parsed case-insensitively and returned in canonical casing. An unrecognised `onStepFailure` returns `400` with `invalid step failure policy`. Both fields are returned by [List project steps](https://www.jawsdeploy.net/rest-api/projects-steps-list), and both are copied into a release when it is created, so a change here applies to releases cut afterwards rather than to ones that already exist. #### Step templates and pinned versions A step runs the version of its step template that it is pinned to, not whatever the template says today. A step added to a project is pinned to the template as it stands at that moment, and it stays there until something moves it. `stepTemplateId` is what moves it. Sending it points the step at that template and pulls the template in: the step's script is replaced with the template's, the property schema is reshaped to the template's, and the pinned version becomes the template's current one. Send the id of the template the step already uses to move the step onto a newer version of it. This is the API equivalent of the **Pull template changes** button in the editor, and it is the supported way to do it - deleting the step and adding it again is not necessary. Property values are kept wherever the property is still in the new schema and still the same kind of control. A value whose property has gone from the template goes with it. `propertiesJson` sent in the same request is applied before the pull, so the values you send are merged the same way. Nothing moves unless there is something to move. If `stepTemplateId` names the template the step already uses and the step is already on its current version, the step is left exactly as it is. Leave `stepTemplateId` out and the template side of the step is never touched, whatever else the request changes - a rename cannot put a step onto a new script. The pinned version is not part of any response, so there is no way to ask which steps are behind their template. Sending `stepTemplateId` on a step that is already current does nothing, so the practical answer is to send it. Watch `runOn` when moving a step to a **different** template. If the step's run mode is not in the new template's `supportedRunModes`, passing it explicitly returns `400`, and leaving it out moves the step to the first run mode the template does support. #### Rolling a step across its machines These four fields shape how a **standalone** step works through the machines of an environment. A step that is a member of a [rolling group](https://www.jawsdeploy.net/rest-api/rolling-groups-list) does not use them. The group carries its own copy of the same settings and that is what the deployment runs, so setting them on a grouped step is not an error - it simply has no effect unless the step leaves the group. Note the defaults differ: a standalone step defaults `onMachineFailure` to `ContinueToOtherMachines`, a group defaults it to `StopStep`. `machineOrder` decides the sequence: - `MachineName` - alphabetical, case-insensitive. The default. - `TagPriority` - a machine's rank is the index of the first tag it carries from `machineOrderTagIds`. A machine matching none of them sorts last rather than being left out. Excluding machines is what `machineIdFilter` and `machineTagFilter` are for. `machineOrderTagIds` is kept only while the order actually uses it. Saving a step whose `machineOrder` is `MachineName` drops the tag list instead of storing it, so a later switch back to `TagPriority` starts from an empty list. That is deliberate - a stale list left lying around would silently resurrect an order you thought you had removed. `waitBetweenMachineGroups` is the barrier. With `TagPriority`, every machine of one rank finishes before the next rank starts, which is what makes a canary rollout a rollout rather than a fast parallel run. `onMachineFailure` decides whether the next machine starts after the step has failed on one. `ContinueToOtherMachines` is the default and the historical behaviour. `StopStep` starts no further machine. Neither ends the deployment, so set it together with `onStepFailure` - a step that stops on the first bad machine while `onStepFailure` is left at `ContinueToNextStep` still lets every later step run. #### `isRolling` is read-only `isRolling` is **not** a field of this request - sending it does nothing. It is reported by [List project steps](https://www.jawsdeploy.net/rest-api/projects-steps-list) and [Get a release](https://www.jawsdeploy.net/rest-api/releases-details) as a summary of the two settings above: it reads `true` when `machineOrder` is not `MachineName`, **or** when `waitBetweenMachineGroups` is on. Configure the order and the barrier, and read `isRolling` back to see what they add up to. Nothing in the deployment runner reads the stored flag. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectStepId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | | 400 | `machineOrder` is not `MachineName` or `TagPriority` - the response reads `invalid machine order`. | | 400 | `onMachineFailure` is not `ContinueToOtherMachines` or `StopStep` - the response reads `invalid machine failure policy`. | Example request: ``` PUT /api/project/step HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic Content-Type: application/json { "projectStepId": "ps_1", "errorAction": "Stop", "onStepFailure": "StopDeployment", "parallelMachines": 4 } // a canary rollout on a standalone step { "projectStepId": "ps_1", "machineOrder": "TagPriority", "machineOrderTagIds": ["canary", "web"], "waitBetweenMachineGroups": true, "onMachineFailure": "StopStep" } ``` Example response: ``` {} ``` ## Delete a project step Source: https://www.jawsdeploy.net/rest-api/project-step-delete | Section: Project Steps Remove a step from a project. `DELETE /api/project/step` Soft-deletes a project step. The step disappears from new releases; existing release snapshots are unaffected. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectStepId` | query | string | yes | ID of the step. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectStepId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/project/step?projectStepId=ps_1 ``` Example response: ``` {} ``` ## Reorder project steps Source: https://www.jawsdeploy.net/rest-api/project-steps-order | Section: Project Steps Replace the order of a project's steps in one call. `PUT /api/project/steps/order` Sets the order of the project's steps. Pass every step ID in the desired order; any step missing from `orderedStepIds` keeps its previous relative position at the end. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | ID of the project. | | `orderedStepIds` | body | array | yes | Step IDs in the desired order. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `projectId` or unknown step ID. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/project/steps/order { "projectId": "prj_abc123", "orderedStepIds": ["ps_2", "ps_1"] } ``` Example response: ``` {} ``` ## List rolling groups Source: https://www.jawsdeploy.net/rest-api/rolling-groups-list | Section: Rolling Groups List a project's rolling groups with their settings and member step ids. `GET /api/project/rolling-groups` Returns every rolling group in the project. A rolling group is a run of consecutive steps that one machine completes before the next machine starts any of them. Each group carries `order` - where it sits among the project's steps, derived from its first member and never set by the caller - and `memberStepIds` in step order, projected from the steps' own `rollingGroupId` so the two cannot disagree. `machineOrderTagIds` is an empty array when the order is `MachineName`. The same membership is visible from the step side on [List project steps](https://www.jawsdeploy.net/rest-api/projects-steps-list), as `rollingGroupId` on each step. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | The project to list groups for. | ### Response fields | Field | Type | Description | |---|---|---| | `projectId` | string | The resolved project id, echoing the one you asked for. | | `rollingGroups` | object[] | The project's rolling groups, ordered by `order`. Empty array when the project has none - not an error. Each element is described below. | | `rollingGroups[].rollingGroupId` | string | The group's id. Pass this as `rollingGroupId` when updating, dissolving, or moving a step into the group. | | `rollingGroups[].projectId` | string | The project the group belongs to. | | `rollingGroups[].name` | string | Display name, shown in the step list, the deployment preview and the logs. | | `rollingGroups[].order` | integer | Where the group sits among the project's steps. **Derived** from the order of its first member and never accepted from the caller, so it cannot disagree with the step list. | | `rollingGroups[].windowSize` | integer | How many machines work through the group at once. `1` means a strict one-at-a-time rollout. Never above the organization's `maxParallelMachines`, which defaults to 8. | | `rollingGroups[].machineOrder` | string (enum) | The order machines enter the group. One of `MachineName` - alphabetical, case-insensitive - or `TagPriority`, where a machine's rank is the index of the first tag it carries from `machineOrderTagIds`. Within a rank machines are ordered by name; machines carrying none of the tags run last. | | `rollingGroups[].machineOrderTagIds` | string[] | Tag ids in priority order, first tag highest. Empty array when `machineOrder` is `MachineName` - the list is not persisted for an order that does not use it. | | `rollingGroups[].waitBetweenMachineGroups` | boolean | The barrier. `true` finishes every machine of one tag rank before the next rank starts. Only ever `true` alongside `machineOrder` of `TagPriority`. | | `rollingGroups[].onMachineFailure` | string (enum) | Whether the next machine starts after the group fails on one. `StopStep` - the default - starts no further machine; `ContinueToOtherMachines` carries on. Neither stops the deployment; that is `onStepFailure` on the member itself. | | `rollingGroups[].onMemberFailure` | string (enum) | Whether a machine runs the group's remaining steps after one fails on it. `SkipRestOnThisMachine` - the default - skips them; `ContinueMembers` runs them, which is what a cleanup or notification member needs. | | `rollingGroups[].memberStepIds` | string[] | The member steps, in step order. **Projected** from the steps' own `rollingGroupId` rather than stored on the group, so this and the step list cannot disagree. Always at least one id. | ### Errors | Status | Meaning | |---|---| | 400 | `projectId` is missing, or names a project that does not exist or is deleted - `invalid project ID`. | | 400 | The group settings failed validation. The body carries `validationErrors`, each with the `property` that failed and its messages. | | 401 | Missing or invalid Basic auth credentials, or the service account may not edit this project. | Example request: ``` GET /api/project/rolling-groups?projectId=prj_42 HTTP/1.1 Authorization: Basic ``` Example response: ``` { "projectId": "prj_42", "rollingGroups": [ { "rollingGroupId": "rg_7a1", "projectId": "prj_42", "name": "web rollout", "order": 3, "windowSize": 1, "machineOrder": "TagPriority", "machineOrderTagIds": ["tag-canary"], "waitBetweenMachineGroups": true, "onMachineFailure": "StopStep", "onMemberFailure": "SkipRestOnThisMachine", "memberStepIds": ["step-1", "step-2", "step-3", "step-4"] } ] } ``` ## Create a rolling group Source: https://www.jawsdeploy.net/rest-api/rolling-group-create | Section: Rolling Groups Group a consecutive run of steps so one machine completes all of them before the next begins. `POST /api/project/rolling-group` Creates a rolling group from a set of member steps. The members are given here rather than added one at a time, because a group with no members cannot exist. Read [List project steps](https://www.jawsdeploy.net/rest-api/projects-steps-list) first. Contiguity and the target-machine rule are properties of the whole ordered step list, so both are checked against it: the steps must be consecutive in `order` with no non-member between them, and every one must have a `runOn` of `TargetMachine`. If a step is in the way, reorder with [Reorder project steps](https://www.jawsdeploy.net/rest-api/project-steps-order) first. Everything after `memberStepIds` is optional. Note the two group defaults that differ from the step equivalents: `onMachineFailure` is `StopStep`, and `windowSize` is 1 - a group exists to take machines out of service one at a time. **Settings live in the release snapshot.** A group created after a release was cut does not apply to that release; create a new one. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | The project the group belongs to. | | `name` | body | string | no | Display name, shown in the step list, deployment preview and logs. Trimmed; maximum 500 characters. Blank or omitted becomes `Rolling group`. | | `memberStepIds` | body | array | yes | The steps to group, in step order. Must be a consecutive run, and every step must run on target machines. | | `windowSize` | body | integer | no | How many machines work through the group at once. Defaults to 1. Capped by the organization's `maxParallelMachines`, which defaults to 8. | | `machineOrder` | body | string | no | `MachineName` (default) or `TagPriority`. | | `machineOrderTagIds` | body | array | no | Ordered tag ids. A machine's rank is the index of the first of these tags it carries. Required when `machineOrder` is `TagPriority`. | | `waitBetweenMachineGroups` | body | boolean | no | The barrier: finish every machine of one tag rank before the next rank starts. Requires `machineOrder` of `TagPriority`. | | `onMachineFailure` | body | string | no | Whether the next machine starts after one fails. `StopStep` (default) or `ContinueToOtherMachines`. | | `onMemberFailure` | body | string | no | Whether a machine runs the group's remaining steps after one fails on it. `SkipRestOnThisMachine` (default) or `ContinueMembers`. | #### Enumerated values Policy and order fields are sent and returned as names, not numbers. Parsing is case-insensitive; responses use the canonical casing. An unrecognised name is rejected rather than silently defaulted. - `machineOrder` - `MachineName`, `TagPriority` - `onMachineFailure` - `ContinueToOtherMachines`, `StopStep` - `onMemberFailure` - `SkipRestOnThisMachine`, `ContinueMembers` ### Response fields | Field | Type | Description | |---|---|---| | `rollingGroupId` | string | The group's id. Pass this as `rollingGroupId` when updating, dissolving, or moving a step into the group. | | `projectId` | string | The project the group belongs to. | | `name` | string | Display name, shown in the step list, the deployment preview and the logs. | | `order` | integer | Where the group sits among the project's steps. **Derived** from the order of its first member and never accepted from the caller, so it cannot disagree with the step list. | | `windowSize` | integer | How many machines work through the group at once. `1` means a strict one-at-a-time rollout. Never above the organization's `maxParallelMachines`, which defaults to 8. | | `machineOrder` | string (enum) | The order machines enter the group. One of `MachineName` - alphabetical, case-insensitive - or `TagPriority`, where a machine's rank is the index of the first tag it carries from `machineOrderTagIds`. Within a rank machines are ordered by name; machines carrying none of the tags run last. | | `machineOrderTagIds` | string[] | Tag ids in priority order, first tag highest. Empty array when `machineOrder` is `MachineName` - the list is not persisted for an order that does not use it. | | `waitBetweenMachineGroups` | boolean | The barrier. `true` finishes every machine of one tag rank before the next rank starts. Only ever `true` alongside `machineOrder` of `TagPriority`. | | `onMachineFailure` | string (enum) | Whether the next machine starts after the group fails on one. `StopStep` - the default - starts no further machine; `ContinueToOtherMachines` carries on. Neither stops the deployment; that is `onStepFailure` on the member itself. | | `onMemberFailure` | string (enum) | Whether a machine runs the group's remaining steps after one fails on it. `SkipRestOnThisMachine` - the default - skips them; `ContinueMembers` runs them, which is what a cleanup or notification member needs. | | `memberStepIds` | string[] | The member steps, in step order. **Projected** from the steps' own `rollingGroupId` rather than stored on the group, so this and the step list cannot disagree. Always at least one id. | ### Errors | Status | Meaning | |---|---| | 400 | `memberStepIds` was empty or omitted - `a rolling group needs at least one member step`. | | 400 | `One or more of the steps to group were not found in this project.` | | 400 | The members are not consecutive. The message names every step sitting between them. | | 400 | A member does not run on target machines - the message names the step and its `runOn`. | | 400 | An unrecognised `machineOrder`, `onMachineFailure` or `onMemberFailure` value. | | 400 | Validation failed: a window outside 1 to the organization maximum, a missing group name, `TagPriority` with no tags, or the barrier without `TagPriority`. | | 401 | Missing or invalid Basic auth credentials, or the service account may not edit this project. | Example request: ``` POST /api/project/rolling-group HTTP/1.1 Authorization: Basic Content-Type: application/json { "projectId": "prj_42", "name": "web rollout", "memberStepIds": ["step-1", "step-2", "step-3", "step-4"], "windowSize": 1, "machineOrder": "TagPriority", "machineOrderTagIds": ["tag-canary"], "waitBetweenMachineGroups": true, "onMachineFailure": "StopStep", "onMemberFailure": "SkipRestOnThisMachine" } ``` Example response: ``` { "rollingGroupId": "rg_7a1", "projectId": "prj_42", "name": "web rollout", "order": 3, "windowSize": 1, "machineOrder": "TagPriority", "machineOrderTagIds": ["tag-canary"], "waitBetweenMachineGroups": true, "onMachineFailure": "StopStep", "onMemberFailure": "SkipRestOnThisMachine", "memberStepIds": ["step-1", "step-2", "step-3", "step-4"] } ``` ## Update a rolling group Source: https://www.jawsdeploy.net/rest-api/rolling-group-update | Section: Rolling Groups Change a group's own settings. Membership is not changed here. `PUT /api/project/rolling-group` Updates a rolling group's own settings. Only the fields you send are applied - an omitted field keeps its current value, and a blank `name` keeps the current name. **Membership is not changed here.** Use [Move a step in or out of a rolling group](https://www.jawsdeploy.net/rest-api/project-step-rolling-group), which is the only path that re-checks the members are still consecutive. There is no `memberStepIds` on this request. **Settings changes require a new release.** A release is a snapshot: changing a group's window and redeploying an existing release deploys the old window. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | The project the group belongs to. | | `rollingGroupId` | body | string | yes | The group to update. Must belong to this project. | | `name` | body | string | no | New display name. Trimmed. Blank or omitted keeps the current name. | | `windowSize` | body | integer | no | How many machines work through the group at once. Omitted leaves it unchanged. | | `machineOrder` | body | string | no | `MachineName` or `TagPriority`. Omitted leaves it unchanged. | | `machineOrderTagIds` | body | array | no | Ordered tag ids. Omitted leaves the current list unchanged. | | `waitBetweenMachineGroups` | body | boolean | no | The barrier. Requires `machineOrder` of `TagPriority`. Omitted leaves it unchanged. | | `onMachineFailure` | body | string | no | `StopStep` or `ContinueToOtherMachines`. Omitted leaves it unchanged. | | `onMemberFailure` | body | string | no | `SkipRestOnThisMachine` or `ContinueMembers`. Omitted leaves it unchanged. | #### Enumerated values Policy and order fields are sent and returned as names, not numbers. Parsing is case-insensitive; responses use the canonical casing. An unrecognised name is rejected rather than silently defaulted. - `machineOrder` - `MachineName`, `TagPriority` - `onMachineFailure` - `ContinueToOtherMachines`, `StopStep` - `onMemberFailure` - `SkipRestOnThisMachine`, `ContinueMembers` ### Response fields | Field | Type | Description | |---|---|---| | `rollingGroupId` | string | The group's id. Pass this as `rollingGroupId` when updating, dissolving, or moving a step into the group. | | `projectId` | string | The project the group belongs to. | | `name` | string | Display name, shown in the step list, the deployment preview and the logs. | | `order` | integer | Where the group sits among the project's steps. **Derived** from the order of its first member and never accepted from the caller, so it cannot disagree with the step list. | | `windowSize` | integer | How many machines work through the group at once. `1` means a strict one-at-a-time rollout. Never above the organization's `maxParallelMachines`, which defaults to 8. | | `machineOrder` | string (enum) | The order machines enter the group. One of `MachineName` - alphabetical, case-insensitive - or `TagPriority`, where a machine's rank is the index of the first tag it carries from `machineOrderTagIds`. Within a rank machines are ordered by name; machines carrying none of the tags run last. | | `machineOrderTagIds` | string[] | Tag ids in priority order, first tag highest. Empty array when `machineOrder` is `MachineName` - the list is not persisted for an order that does not use it. | | `waitBetweenMachineGroups` | boolean | The barrier. `true` finishes every machine of one tag rank before the next rank starts. Only ever `true` alongside `machineOrder` of `TagPriority`. | | `onMachineFailure` | string (enum) | Whether the next machine starts after the group fails on one. `StopStep` - the default - starts no further machine; `ContinueToOtherMachines` carries on. Neither stops the deployment; that is `onStepFailure` on the member itself. | | `onMemberFailure` | string (enum) | Whether a machine runs the group's remaining steps after one fails on it. `SkipRestOnThisMachine` - the default - skips them; `ContinueMembers` runs them, which is what a cleanup or notification member needs. | | `memberStepIds` | string[] | The member steps, in step order. **Projected** from the steps' own `rollingGroupId` rather than stored on the group, so this and the step list cannot disagree. Always at least one id. | ### Errors | Status | Meaning | |---|---| | 400 | `rollingGroupId` is missing, unknown, or belongs to another project - `invalid rolling group ID`. | | 400 | An unrecognised `machineOrder`, `onMachineFailure` or `onMemberFailure` value. | | 400 | Validation failed - the same rules as create. | | 401 | Missing or invalid Basic auth credentials, or the service account may not edit this project. | Example request: ``` PUT /api/project/rolling-group HTTP/1.1 Authorization: Basic Content-Type: application/json { "projectId": "prj_42", "rollingGroupId": "rg_7a1", "windowSize": 2, "onMachineFailure": "ContinueToOtherMachines" } ``` Example response: ``` { "rollingGroupId": "rg_7a1", "projectId": "prj_42", "name": "web rollout", "order": 3, "windowSize": 2, "machineOrder": "TagPriority", "machineOrderTagIds": ["tag-canary"], "waitBetweenMachineGroups": true, "onMachineFailure": "ContinueToOtherMachines", "onMemberFailure": "SkipRestOnThisMachine", "memberStepIds": ["step-1", "step-2", "step-3", "step-4"] } ``` ## Dissolve a rolling group Source: https://www.jawsdeploy.net/rest-api/rolling-group-delete | Section: Rolling Groups Dissolve a group. Its steps are kept and released from it. `DELETE /api/project/rolling-group` Dissolves a rolling group. Its steps stay in the project and keep their order - they simply stop being rolled together. Dissolving a group is not a reason to delete the work it grouped. Returns an empty body; success is the status code. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | query | string | yes | The project the group belongs to. | | `rollingGroupId` | query | string | yes | The group to dissolve. | #### Response body None. Success is the `200` status code and the body is empty. ### Errors | Status | Meaning | |---|---| | 400 | `rollingGroupId` was missing or blank - `invalid rolling group ID`. | | 400 | `projectId` is missing or names a project that does not exist. | | 401 | Missing or invalid Basic auth credentials, or the service account may not edit this project. | Example request: ``` DELETE /api/project/rolling-group?projectId=prj_42&rollingGroupId=rg_7a1 HTTP/1.1 Authorization: Basic ``` Example response: ``` HTTP/1.1 200 OK (empty body) ``` ## Move a step in or out of a rolling group Source: https://www.jawsdeploy.net/rest-api/project-step-rolling-group | Section: Rolling Groups Move one step into a group, or out of the group it is in. `PUT /api/project/step/rolling-group` Moves one step into a rolling group, or takes it out when `rollingGroupId` is omitted, null or empty. Membership and position are applied together, because changing one without the other cannot be expressed validly: taking a step out of the middle of a run would leave the members either side of it no longer adjacent. A step joining is appended to the end of the group's run, and a step leaving is placed immediately after the group, so the remaining members stay consecutive. **A group left with no members is removed.** The steps themselves are never deleted. Returns an empty body. Call [List rolling groups](https://www.jawsdeploy.net/rest-api/rolling-groups-list) or [List project steps](https://www.jawsdeploy.net/rest-api/projects-steps-list) to read back the resulting membership and order. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `projectId` | body | string | yes | The project the step belongs to. | | `projectStepId` | body | string | yes | The step to move. | | `rollingGroupId` | body | string | no | The group to move the step into. Omit, or send null or an empty string, to take the step out of its group. | #### Response body None. Success is the `200` status code and the body is empty. ### Errors | Status | Meaning | |---|---| | 400 | `projectStepId` was missing or blank - `invalid project step ID`. | | 400 | `That rolling group does not belong to this project.` | | 400 | The step would not end up next to the group's other members. The message names the steps sitting in between. | | 400 | The step does not run on target machines - the message names it and its `runOn`. | | 401 | Missing or invalid Basic auth credentials, or the service account may not edit this project. | Example request: ``` PUT /api/project/step/rolling-group HTTP/1.1 Authorization: Basic Content-Type: application/json { "projectId": "prj_42", "projectStepId": "step-5", "rollingGroupId": "rg_7a1" } # Take the step back out: { "projectId": "prj_42", "projectStepId": "step-5", "rollingGroupId": "" } ``` Example response: ``` HTTP/1.1 200 OK (empty body) ``` ## List environments Source: https://www.jawsdeploy.net/rest-api/environments-list | Section: Environments List the deployment environments in a workspace, and whether each one is accepting deployments. `GET /api/environment` Returns the workspace's environments with their sort order, display colour and whether each one is currently accepting deployments. This is the endpoint that answers *why* a deploy was refused for an environment: `enabled` being `false` means it was switched off with [Update an environment](https://www.jawsdeploy.net/rest-api/environments-update), as opposed to being blocked by lifecycle phase progression, which the deploy error alone does not distinguish. Deleted environments are not returned. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Response fields | Field | Type | Description | |---|---|---| | `workspaceId` | string | The workspace the environments belong to, echoing the one you asked for. | | `environments` | object[] | The workspace's environments. Empty array when it has none - not an error. | | `environments[].environmentId` | string | The environment's id. This is the value every other endpoint takes as `environmentId`. | | `environments[].name` | string | Display name, unique within the workspace. | | `environments[].sortOrder` | integer | Display order in the UI. Not a deployment order. | | `environments[].color` | string | Hex colour of the environment chip, or `null` when none is set. | | `environments[].enabled` | boolean | `false` when the environment has been switched off and will refuse deployments. See [Update an environment](https://www.jawsdeploy.net/rest-api/environments-update). | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/environment?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "environments": [ { "environmentId": "env_a", "name": "Production", "sortOrder": 30, "color": "#ff6b6b", "enabled": true }, { "environmentId": "env_b", "name": "Staging", "sortOrder": 20, "color": "#ffb454", "enabled": false } ] } ``` ## Create an environment Source: https://www.jawsdeploy.net/rest-api/environments-create | Section: Environments Create a new deployment environment in a workspace. `POST /api/environment` Creates an environment. Environments group machines and cloud targets that receive deployments together. A new environment is live as soon as it exists. Pass `enabled` as `false` to create one that cannot yet receive deployments - useful when the machines are not assigned yet - and turn it on later with [Update an environment](https://www.jawsdeploy.net/rest-api/environments-update). ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Environment name (must be unique within the workspace). | | `sortOrder` | body | integer | no | Display order in the UI. | | `color` | body | string | no | Hex color used for the environment chip. | | `enabled` | body | boolean | no | Whether the environment can receive deployments. Defaults to `true`, so an environment created without this field is live immediately. Pass `false` to stage one before opening it up. See [Update an environment](https://www.jawsdeploy.net/rest-api/environments-update) for what the flag does. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId` or validation failure (e.g. duplicate name). | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/environment HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic Content-Type: application/json { "workspaceId": "ws_abc", "name": "Staging", "sortOrder": 20, "color": "#ffb454", "enabled": true } ``` Example response: ``` { "environmentId": "env_b" } ``` ## Update an environment Source: https://www.jawsdeploy.net/rest-api/environments-update | Section: Environments Rename an environment, restyle it, reorder it, or take it out of service. `PUT /api/environment` Updates an environment in place. Pass only the fields you want to change - `name`, `sortOrder`, `color` and `enabled` are each left alone when you leave them out. Renaming an environment does not affect releases that already targeted it. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `environmentId` | body | string | yes | ID of the environment. | | `name` | body | string | no | New name. | | `sortOrder` | body | integer | no | New display order. | | `color` | body | string | no | New hex color. | | `enabled` | body | boolean | no | Set to `false` to stop the environment receiving deployments, or `true` to let it again. **Omit it and the current value is kept.** See below. | #### Disabling an environment `enabled` is the switch for taking an environment out of service without deleting it. It is the field to reach for during a maintenance window, or to freeze production while an incident is open. The environment keeps everything else. Its machines stay assigned, its lifecycle phases still name it, its variables keep their scoping, and its deployment history is untouched. Re-enable it and it carries on. That is the difference between this and [Delete an environment](https://www.jawsdeploy.net/rest-api/environments-delete), which is a soft delete the environment does not come back from. **What a disabled environment refuses:** - A [deploy](https://www.jawsdeploy.net/rest-api/releases-deploy) or [promote](https://www.jawsdeploy.net/rest-api/releases-promote) naming it fails up front with `400`, and the message lists the environment among those that are not available. The same message covers environments blocked by lifecycle phase progression, so a disabled environment and a not-yet-reachable one read alike - check `enabled` on [List environments](https://www.jawsdeploy.net/rest-api/environments-list) to tell them apart. - A lifecycle will not promote a release into it automatically. **What it does not do:** disabling is not a stop button for work already under way. A deployment that was queued before the environment was disabled is not cancelled. It fails when it is picked up, logging `Deployment blocked: the environment is disabled.` and finishing as `Failed`. To stop something already running, use [Cancel a deployment](https://www.jawsdeploy.net/rest-api/deployments-cancel) as well. Environments are enabled when created unless [Create an environment](https://www.jawsdeploy.net/rest-api/environments-create) is told otherwise, and every environment that existed before this flag was added is enabled. The current value is returned by [List environments](https://www.jawsdeploy.net/rest-api/environments-list). ### Errors | Status | Meaning | |---|---| | 400 | Invalid `environmentId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/environment HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic Content-Type: application/json { "environmentId": "env_b", "enabled": false, "color": "#7ee787" } ``` Example response: ``` {} ``` ## Delete an environment Source: https://www.jawsdeploy.net/rest-api/environments-delete | Section: Environments Soft-delete an environment. `DELETE /api/environment` Soft-deletes the environment. New deployments cannot target it; historical data is preserved. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `environmentId` | query | string | yes | ID of the environment. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `environmentId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/environment?environmentId=env_b ``` Example response: ``` {} ``` ## List machines Source: https://www.jawsdeploy.net/rest-api/machines-list | Section: Machines List the machines in a workspace with the environments each one is assigned to. `GET /api/machine` Lists every machine (deployment agent) in the workspace, each with the environments it is assigned to and the tags it carries. Machines are **not created through this API**. A machine appears here once the Jaws agent has been installed on it and has completed its handshake with the server, which is also where its URL, credentials and communication mode are settled. What the API owns is the assignment: which environments the machine takes part in. Use this endpoint to resolve the `machineId` values the other endpoints in this group take. Deleted machines are never returned. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to manage machines in this workspace. | Example request: ``` GET /api/machine?workspaceId=ws_abc HTTP/1.1 Authorization: Basic ``` Example response: ``` { "workspaceId": "ws_abc", "machines": [ { "machineId": "mch_web01", "name": "web-01", "description": "Front-end web server", "isActive": true, "environments": [ { "environmentId": "env_prod", "name": "Production", "enabled": true } ], "tags": [ { "tagId": "tag_web", "name": "web" } ] } ] } ``` ## List a machine's environments Source: https://www.jawsdeploy.net/rest-api/machines-environments-list | Section: Machines Return the environments one machine is currently assigned to. `GET /api/machine/environments` Returns the environments a single machine is assigned to. The same set is visible from the workspace side on [List machines](https://www.jawsdeploy.net/rest-api/machines-list); this endpoint is the cheaper read when you already hold a `machineId` and only want that one machine. A machine only takes part in a deployment to an environment it is assigned to, so this list is what decides whether a given deployment will reach it. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `machineId` | query | string | yes | ID of the machine, from [List machines](https://www.jawsdeploy.net/rest-api/machines-list). | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `machineId`. A machine that belongs to another organization is reported the same way as one that does not exist. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to manage machines in this workspace. | Example request: ``` GET /api/machine/environments?machineId=mch_web01 HTTP/1.1 Authorization: Basic ``` Example response: ``` { "machineId": "mch_web01", "workspaceId": "ws_abc", "environments": [ { "environmentId": "env_stage", "name": "Staging", "enabled": true }, { "environmentId": "env_prod", "name": "Production", "enabled": true } ] } ``` ## Replace a machine's environments Source: https://www.jawsdeploy.net/rest-api/machines-environments-replace | Section: Machines Set the complete list of environments a machine is assigned to. `PUT /api/machine/environments` Replaces the machine's environments with **exactly** the ones you send. Environments the machine currently has that are not in the list are unassigned, and any in the list that it does not have are added. Send an empty `environmentIds` array to clear every assignment, which takes the machine out of service for new deployments without deleting it. This is the endpoint to use when your pipeline owns the whole picture and wants the machine to end up in a known state. To change one assignment while leaving the rest alone, use [Assign an environment](https://www.jawsdeploy.net/rest-api/machines-environment-assign) or [Unassign an environment](https://www.jawsdeploy.net/rest-api/machines-environment-unassign) instead. Every ID must name an environment in the **same workspace as the machine**. If any one of them does not, the whole call is refused and nothing is changed - the replacement is all or nothing. Duplicate IDs are collapsed rather than rejected. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `machineId` | body | string | yes | ID of the machine, from [List machines](https://www.jawsdeploy.net/rest-api/machines-list). | | `environmentIds` | body | array | yes | The complete set of environment IDs the machine should be assigned to. Send `[]` to clear all assignments. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `machineId`. A machine that belongs to another organization is reported the same way as one that does not exist. | | 400 | One or more environment IDs are invalid for this workspace. Nothing is changed. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to manage machines in this workspace. | Example request: ``` PUT /api/machine/environments HTTP/1.1 Authorization: Basic Content-Type: application/json { "machineId": "mch_web01", "environmentIds": ["env_stage", "env_prod"] } ``` Example response: ``` {} ``` ## Assign an environment to a machine Source: https://www.jawsdeploy.net/rest-api/machines-environment-assign | Section: Machines Add one environment to a machine, leaving its other assignments alone. `POST /api/machine/environment` Assigns one environment to the machine. The machine's other environments are left untouched, so this is the endpoint for bringing a machine into a tier without having to know the rest of its assignments. The call is **idempotent**: assigning an environment the machine already has succeeds and changes nothing, so a pipeline that reruns does not have to check first. The environment must belong to the same workspace as the machine. A disabled environment can still be assigned - `enabled` controls whether the environment takes part in deployments, not whether it can be configured. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `machineId` | body | string | yes | ID of the machine, from [List machines](https://www.jawsdeploy.net/rest-api/machines-list). | | `environmentId` | body | string | yes | ID of the environment, from [List environments](https://www.jawsdeploy.net/rest-api/environments-list). | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `machineId`. A machine that belongs to another organization is reported the same way as one that does not exist. | | 400 | Missing `environmentId`, or an environment that does not belong to the machine's workspace. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to manage machines in this workspace. | Example request: ``` POST /api/machine/environment HTTP/1.1 Authorization: Basic Content-Type: application/json { "machineId": "mch_web01", "environmentId": "env_prod" } ``` Example response: ``` {} ``` ## Unassign an environment from a machine Source: https://www.jawsdeploy.net/rest-api/machines-environment-unassign | Section: Machines Remove one environment from a machine, leaving its other assignments alone. `DELETE /api/machine/environment` Removes one environment from the machine, leaving its other environments in place. The machine itself is not deleted and keeps its registration, so it can be assigned again later. The call is **idempotent**: removing an environment the machine was never assigned to succeeds and changes nothing, so a pipeline can unassign without checking first. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `machineId` | query | string | yes | ID of the machine, from [List machines](https://www.jawsdeploy.net/rest-api/machines-list). | | `environmentId` | query | string | yes | ID of the environment to remove from the machine. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `machineId`. A machine that belongs to another organization is reported the same way as one that does not exist. | | 400 | Missing `environmentId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks permission to manage machines in this workspace. | Example request: ``` DELETE /api/machine/environment?machineId=mch_web01&environmentId=env_prod HTTP/1.1 Authorization: Basic ``` Example response: ``` {} ``` ## List lifecycles Source: https://www.jawsdeploy.net/rest-api/lifecycles-list | Section: Lifecycles List the release lifecycles defined in a workspace. `GET /api/lifecycle` Returns each lifecycle with its ordered phases and per-phase environments. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/lifecycle?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "lifecycles": [ { "lifecycleId": "lc_1", "name": "default", "phases": [ { "phaseId": "ph_1", "name": "Dev", "environments": [...] } ] } ] } ``` ## Get lifecycle details Source: https://www.jawsdeploy.net/rest-api/lifecycles-details | Section: Lifecycles Retrieve one lifecycle by ID. `GET /api/lifecycle/details` Returns one lifecycle: its name, description, ordered phases, and the environments in each phase. The lifecycle ID identifies its workspace, so no `workspaceId` is required. Phase fields - `progressRequirement`, `progressRequirementErrorMode`, `progressRequirementCount`, and `howToTrigger` - are described in [Add a lifecycle phase](https://www.jawsdeploy.net/rest-api/lifecycle-phase-add). ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | query | string | yes | ID of the lifecycle. | ### Errors | Status | Meaning | |---|---| | 400 | Missing or unknown `lifecycleId`. | | 401 | Missing or invalid Basic auth credentials, or the service account cannot manage lifecycles in this workspace. | Example request: ``` GET /api/lifecycle/details?lifecycleId=lc_1 HTTP/1.1 Authorization: Basic ``` Example response: ``` { "lifecycleId": "lc_1", "workspaceId": "ws_abc", "name": "default", "description": "Standard promotion path", "phases": [ { "phaseId": "ph_1", "name": "Dev", "description": null, "sortOrder": 1, "progressRequirement": "AllMustComplete", "progressRequirementErrorMode": "NoErrorsNoWarnings", "progressRequirementCount": 0, "environments": [ { "environmentId": "env_a", "environmentName": "Development", "howToTrigger": "Automatic" } ] }, { "phaseId": "ph_2", "name": "Staging", "description": "Sign-off before production", "sortOrder": 2, "progressRequirement": "MinimumNumberToComplete", "progressRequirementErrorMode": "NoErrorsAllowWarnings", "progressRequirementCount": 1, "environments": [ { "environmentId": "env_b", "environmentName": "Staging EU", "howToTrigger": "Manual" }, { "environmentId": "env_c", "environmentName": "Staging US", "howToTrigger": "Manual" } ] } ] } ``` ## Create a lifecycle Source: https://www.jawsdeploy.net/rest-api/lifecycles-create | Section: Lifecycles Create a new release lifecycle. `POST /api/lifecycle` Creates a new lifecycle. Pass `phases` to seed the lifecycle in one call - each phase lists environments and an optional progress requirement (`AllMustComplete`, `MinimumNumberToComplete`, `OptionalPhase`). Phases can also be added or edited later via the `/api/lifecycle/phase` endpoints. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Lifecycle name. | | `description` | body | string | no | Optional description. | | `phases` | body | array | no | Ordered phases. See [phase add](https://www.jawsdeploy.net/rest-api/lifecycle-phase-add) for the phase object shape. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`, validation failure, or one of the referenced environment IDs not in the workspace. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/lifecycle { "workspaceId": "ws_abc", "name": "default", "phases": [ { "name": "Dev", "environments": [ { "environmentId": "env_a", "howToTrigger": "Automatic" } ] } ] } ``` Example response: ``` { "lifecycleId": "lc_1" } ``` ## Update a lifecycle Source: https://www.jawsdeploy.net/rest-api/lifecycles-update | Section: Lifecycles Update a lifecycle's name, description, or phase list. `PUT /api/lifecycle` Update lifecycle metadata. If `phases` is provided it fully replaces the existing phase list (and validates each environment ID against the workspace). Send `knownPhaseIds` alongside it to be told when somebody else changed the phases first, rather than overwriting their work - see below. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | body | string | yes | ID of the lifecycle. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `phases` | body | array | no | Replacement phase list (optional). | | `knownPhaseIds` | body | array | no | The phase IDs you believe the lifecycle currently has. Send it to be told about a concurrent change instead of silently overwriting it. Omit it to skip the check. See below. | #### Not overwriting somebody else's phases Sending `phases` replaces the **whole** list, so a stored phase you leave out is deleted. That is right when you meant to remove it, and destructive when you simply never knew it was there - a caller that read the lifecycle before somebody else added a phase looks exactly like a caller that deleted one. `knownPhaseIds` tells the two apart. Send back the phase IDs you got when you read the lifecycle, and the update is refused if the stored set has moved on - phases added by somebody else since you read it, and phases they removed, both count. A mismatch returns `409` with an `errorcode` of `ConcurrentModification` and a message saying how many phases were added and how many removed. **Nothing is written.** The comparison runs inside the same transaction as the write, so it cannot race the thing it guards. Re-read the lifecycle with [Get lifecycle details](https://www.jawsdeploy.net/rest-api/lifecycles-details), apply your change to what is actually there, and send it again with the fresh IDs. Omitting `knownPhaseIds` skips the check and last write wins. That is a fair choice for a pipeline that owns its lifecycle outright, and the wrong one anywhere a person might be editing the same lifecycle in the UI at the same time. IDs are compared as GUIDs, so casing and surrounding braces do not matter - `{A1B2...}` and `a1b2...` are the same phase. Send them back as you received them and this never arises. This is the only endpoint that replaces the whole phase list, so it is the only one that needs a baseline from the caller. [Add](https://www.jawsdeploy.net/rest-api/lifecycle-phase-add), [update](https://www.jawsdeploy.net/rest-api/lifecycle-phase-update) and [delete](https://www.jawsdeploy.net/rest-api/lifecycle-phase-delete) each name a single phase by ID and build their own baseline server-side, so none of them can remove a phase you never knew about. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `lifecycleId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | | 409 | `knownPhaseIds` no longer matches the lifecycle's stored phases. `errorcode` is `ConcurrentModification` and the message says how many phases were added and how many removed. Nothing is written - re-read the lifecycle and send the change again. | Example request: ``` PUT /api/lifecycle HTTP/1.1 Host: app.jawsdeploy.net Authorization: Basic Content-Type: application/json { "lifecycleId": "lc_1", "description": "Default flow", "knownPhaseIds": ["lph_1", "lph_2"] } ``` Example response: ``` {} ``` ## Delete a lifecycle Source: https://www.jawsdeploy.net/rest-api/lifecycles-delete | Section: Lifecycles Delete a lifecycle. `DELETE /api/lifecycle` Deletes a lifecycle. Channels that bind to it must be updated separately. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | query | string | yes | ID of the lifecycle. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `lifecycleId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/lifecycle?lifecycleId=lc_1 ``` Example response: ``` {} ``` ## Add a lifecycle phase Source: https://www.jawsdeploy.net/rest-api/lifecycle-phase-add | Section: Lifecycles Append a phase to a lifecycle. `POST /api/lifecycle/phase` Adds a phase to a lifecycle. The phase is appended at the end unless `sortOrder` is provided. Each phase environment has a `howToTrigger` mode: `Manual` (the default) or `Automatic`. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | body | string | yes | ID of the lifecycle. | | `name` | body | string | yes | Phase name. | | `description` | body | string | no | Optional description. | | `sortOrder` | body | integer | no | Position. Defaults to end. | | `progressRequirement` | body | string | no | How many of the phase environments must complete before the release can move on. One of `AllMustComplete` (the default), `MinimumNumberToComplete`, or `OptionalPhase`. See below for what each one means. | | `progressRequirementCount` | body | integer | no | How many environments must complete. Only used when `progressRequirement` is `MinimumNumberToComplete`, and discarded otherwise. | | `progressRequirementErrorMode` | body | string | no | How a finished deployment is judged when counting phase progress. One of `NoErrorsNoWarnings` (the default), `NoErrorsAllowWarnings`, or `Always`. See below for what each one means. | | `environments` | body | array | no | List of `{ environmentId, howToTrigger }`. | | `phaseId` | body | string | no | Ignored when adding - the new phase always gets a new ID, returned in the response. | #### Progress requirements `progressRequirement` decides how many of the phase environments must record a counted deployment before the release may move on. One of: - `AllMustComplete` - the default. Every environment in the phase must complete. - `MinimumNumberToComplete` - only `progressRequirementCount` of them must complete. The rest stay deployable but no longer hold the release back. - `OptionalPhase` - nothing is required. Following phases become eligible immediately, and `progressRequirementErrorMode` is stored as `Unset`. `progressRequirementCount` is kept only for `MinimumNumberToComplete`. Under the other two it is discarded and reads back as `0`. It is not checked against the number of environments in the phase, and a count of `0` leaves the phase requiring nothing, exactly like `OptionalPhase`. #### Progress error modes `progressRequirementErrorMode` decides whether a finished deployment counts towards its phase's progress. A deployment that never reached `Completed` does not count, whatever the mode. - `NoErrorsNoWarnings` - the default. The deployment counts only if it finished with no errors **and** no warnings. - `NoErrorsAllowWarnings` - the deployment counts if it finished with no errors. Warnings are ignored. - `Always` - every completed deployment counts, whatever its error and warning counts. - `Unset` - what the server stores for an optional phase (`progressRequirement = OptionalPhase`), where the mode has no meaning. It is accepted on input and behaves like `Always`, but prefer sending one of the three modes above. Values are matched case insensitively, so `always` and `Always` are equivalent, but the name must be spelled out in full - a bare number such as `1` is rejected. An unrecognised value fails the request with `400` and `errorcode = InvalidParameter`. The message names the field, the value you sent, and the full list of valid values, and every bad enum field in the request is reported together. Omitting the field is not an error: the phase is created with `NoErrorsNoWarnings`. Setting `progressRequirement` to `OptionalPhase` overwrites the stored mode with `Unset`. If you later move that phase back to a non-optional requirement, send `progressRequirementErrorMode` in the same request - otherwise the phase keeps `Unset` and every completed deployment counts. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `lifecycleId`, validation failure, or referenced environment not in the workspace. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | | 400 | An enum field was sent with a value that is not one of its defined names, for example `progressRequirementErrorMode` or `environments[].howToTrigger`. The message names each rejected field, the value sent, and the valid values for that field. | Example request: ``` POST /api/lifecycle/phase { "lifecycleId": "lc_1", "name": "Staging", "environments": [ { "environmentId": "env_b", "howToTrigger": "Manual" } ] } ``` Example response: ``` { "phaseId": "ph_2" } ``` ## Update a lifecycle phase Source: https://www.jawsdeploy.net/rest-api/lifecycle-phase-update | Section: Lifecycles Update name, requirements, or environments of a phase. `PUT /api/lifecycle/phase` Updates a phase. Pass only the fields you want to change. If `environments` is set, it fully replaces the phase's environments. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | body | string | yes | ID of the lifecycle. | | `phaseId` | body | string | yes | ID of the phase. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `sortOrder` | body | integer | no | New position. | | `progressRequirement` | body | string | no | New progress requirement: `AllMustComplete`, `MinimumNumberToComplete`, or `OptionalPhase`. Omit to leave the stored requirement unchanged. See below for what each one means. | | `progressRequirementCount` | body | integer | no | New required count. Only used when `progressRequirement` is `MinimumNumberToComplete`, and discarded otherwise. | | `progressRequirementErrorMode` | body | string | no | New error mode: `NoErrorsNoWarnings`, `NoErrorsAllowWarnings`, or `Always`. Omit to leave the stored mode unchanged. See below for what each one means. | | `environments` | body | array | no | Replacement environment list. | #### Progress requirements `progressRequirement` decides how many of the phase environments must record a counted deployment before the release may move on. One of: - `AllMustComplete` - the default. Every environment in the phase must complete. - `MinimumNumberToComplete` - only `progressRequirementCount` of them must complete. The rest stay deployable but no longer hold the release back. - `OptionalPhase` - nothing is required. Following phases become eligible immediately, and `progressRequirementErrorMode` is stored as `Unset`. `progressRequirementCount` is kept only for `MinimumNumberToComplete`. Under the other two it is discarded and reads back as `0`. It is not checked against the number of environments in the phase, and a count of `0` leaves the phase requiring nothing, exactly like `OptionalPhase`. #### Progress error modes `progressRequirementErrorMode` decides whether a finished deployment counts towards its phase's progress. A deployment that never reached `Completed` does not count, whatever the mode. - `NoErrorsNoWarnings` - the default for a newly added phase. The deployment counts only if it finished with no errors **and** no warnings. - `NoErrorsAllowWarnings` - the deployment counts if it finished with no errors. Warnings are ignored. - `Always` - every completed deployment counts, whatever its error and warning counts. - `Unset` - what the server stores for an optional phase (`progressRequirement = OptionalPhase`), where the mode has no meaning. It is accepted on input and behaves like `Always`, but prefer sending one of the three modes above. Values are matched case insensitively, so `always` and `Always` are equivalent, but the name must be spelled out in full - a bare number such as `1` is rejected. An unrecognised value fails the request with `400` and `errorcode = InvalidParameter`, and the phase is left exactly as it was. The message names the field, the value you sent, and the full list of valid values, and every bad enum field in the request is reported together. Omitting the field is not an error: the phase keeps its stored mode. Setting `progressRequirement` to `OptionalPhase` overwrites the stored mode with `Unset`. If you later move that phase back to a non-optional requirement, send `progressRequirementErrorMode` in the same request - otherwise the phase keeps `Unset` and every completed deployment counts. ### Errors | Status | Meaning | |---|---| | 400 | Invalid IDs or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | | 400 | An enum field was sent with a value that is not one of its defined names, for example `progressRequirementErrorMode` or `environments[].howToTrigger`. The message names each rejected field, the value sent, and the valid values for that field. The phase is left unchanged. | Example request: ``` PUT /api/lifecycle/phase { "lifecycleId": "lc_1", "phaseId": "ph_2", "progressRequirement": "AllMustComplete" } ``` Example response: ``` {} ``` ## Delete a lifecycle phase Source: https://www.jawsdeploy.net/rest-api/lifecycle-phase-delete | Section: Lifecycles Remove a phase from a lifecycle. `DELETE /api/lifecycle/phase` Removes a phase from a lifecycle. The lifecycle stays valid and the remaining phases keep their order. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | query | string | yes | ID of the lifecycle. | | `phaseId` | query | string | yes | ID of the phase to remove. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `lifecycleId` or `phaseId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/lifecycle/phase?lifecycleId=lc_1&phaseId=ph_2 ``` Example response: ``` {} ``` ## Upsert phase environment Source: https://www.jawsdeploy.net/rest-api/lifecycle-phase-environment-upsert | Section: Lifecycles Attach an environment to a phase or change its trigger mode. `PUT /api/lifecycle/phase/environment` Adds an environment to a phase if not already present, otherwise updates its `howToTrigger` mode. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | body | string | yes | ID of the lifecycle. | | `phaseId` | body | string | yes | ID of the phase. | | `environmentId` | body | string | yes | ID of the environment. | | `howToTrigger` | body | string | no | `Manual` (the default) or `Automatic`. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid IDs or environment not in the workspace. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/lifecycle/phase/environment { "lifecycleId": "lc_1", "phaseId": "ph_2", "environmentId": "env_b", "howToTrigger": "Automatic" } ``` Example response: ``` {} ``` ## Detach phase environment Source: https://www.jawsdeploy.net/rest-api/lifecycle-phase-environment-delete | Section: Lifecycles Remove an environment from a phase. `DELETE /api/lifecycle/phase/environment` Detaches an environment from a phase. The environment is not deleted. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `lifecycleId` | query | string | yes | ID of the lifecycle. | | `phaseId` | query | string | yes | ID of the phase. | | `environmentId` | query | string | yes | ID of the environment. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid IDs. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/lifecycle/phase/environment?lifecycleId=lc_1&phaseId=ph_2&environmentId=env_b ``` Example response: ``` {} ``` ## List feeds Source: https://www.jawsdeploy.net/rest-api/feeds-list | Section: Feeds List package feeds in a workspace. `GET /api/feed` Lists every feed configured in the workspace, including the built-in Jaws Deploy feed and any external feeds (NuGet, etc.). ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/feed?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "feeds": [ { "feedId": "feed_1", "name": "Acme NuGet", "type": "NuGet", "location": "https://nuget.acme.io/v3/index.json", "username": "ci" } ] } ``` ## Create a feed Source: https://www.jawsdeploy.net/rest-api/feeds-create | Section: Feeds Register a new external feed in a workspace. `POST /api/feed` Creates an external package feed. Creating the built-in `Jaws` feed type is not allowed. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Display name. | | `type` | body | string | yes | Feed type, e.g. `NuGet`. | | `location` | body | string | yes | Feed URL. | | `username` | body | string | no | Optional username. | | `password` | body | string | no | Optional password / API key. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`, invalid `type`, or trying to create the built-in `Jaws` feed. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/feed { "workspaceId": "ws_abc", "name": "Acme NuGet", "type": "NuGet", "location": "https://nuget.acme.io/v3/index.json" } ``` Example response: ``` { "feedId": "feed_1" } ``` ## Update a feed Source: https://www.jawsdeploy.net/rest-api/feeds-update | Section: Feeds Update an external feed's connection details. `PUT /api/feed` Updates a feed. The built-in `Jaws` feed is not editable. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `feedId` | body | string | yes | ID of the feed. | | `name` | body | string | no | New name. | | `type` | body | string | no | New feed type. | | `location` | body | string | no | New URL. | | `username` | body | string | no | New username. | | `password` | body | string | no | New password / API key. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `feedId`, attempt to switch a feed to type `Jaws`, or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/feed { "feedId": "feed_1", "password": "***" } ``` Example response: ``` {} ``` ## Delete a feed Source: https://www.jawsdeploy.net/rest-api/feeds-delete | Section: Feeds Delete an external feed. `DELETE /api/feed` Soft-deletes the feed. Built-in `Jaws` feeds cannot be removed. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `feedId` | query | string | yes | ID of the feed. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `feedId` or built-in `Jaws` feed. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/feed?feedId=feed_1 ``` Example response: ``` {} ``` ## List cloud accounts Source: https://www.jawsdeploy.net/rest-api/cloud-accounts-list | Section: Cloud Accounts List cloud accounts configured in a workspace. `GET /api/cloud-account` Returns every cloud account (currently Azure only) configured for the workspace, including optional environment filter scoping. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/cloud-account?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "accounts": [ { "accountId": "ca_1", "name": "Acme prod sub", "cloudType": "Azure", "applyEnvironmentFilter": true, "environmentFilter": ["env_prod"] } ] } ``` ## Create a cloud account Source: https://www.jawsdeploy.net/rest-api/cloud-accounts-create | Section: Cloud Accounts Register an Azure cloud account in a workspace. `POST /api/cloud-account` Creates a cloud account. Currently only `Azure` is supported - pass the service principal credentials in the `azure` sub-object. If `applyEnvironmentFilter` is true, the account is restricted to deployments targeting one of `environmentFilter`. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Display name. | | `description` | body | string | no | Optional description. | | `cloudType` | body | string | no | `Azure` (default). | | `applyEnvironmentFilter` | body | boolean | no | Restrict use of this account to specific environments. | | `environmentFilter` | body | array | no | Environment IDs allowed to use this account. | | `azure` | body | object | no | `{ subscriptionId, tenantId, applicationId, applicationPassword }`. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`, invalid `cloudType`, or validation failure (e.g. bad environment IDs). | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/cloud-account { "workspaceId": "ws_abc", "name": "Acme prod sub", "cloudType": "Azure", "azure": { "subscriptionId": "...", "tenantId": "...", "applicationId": "...", "applicationPassword": "..." } } ``` Example response: ``` { "accountId": "ca_1" } ``` ## Update a cloud account Source: https://www.jawsdeploy.net/rest-api/cloud-accounts-update | Section: Cloud Accounts Update a cloud account's settings or credentials. `PUT /api/cloud-account` Updates a cloud account. Pass only the fields you want to change. To rotate credentials, send a new `azure` object. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `accountId` | body | string | yes | ID of the cloud account. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `cloudType` | body | string | no | New cloud type. | | `applyEnvironmentFilter` | body | boolean | no | Toggle environment filtering. | | `environmentFilter` | body | array | no | New environments filter. | | `azure` | body | object | no | Replacement Azure credentials. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `accountId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/cloud-account { "accountId": "ca_1", "azure": { "applicationPassword": "***" } } ``` Example response: ``` {} ``` ## Delete a cloud account Source: https://www.jawsdeploy.net/rest-api/cloud-accounts-delete | Section: Cloud Accounts Soft-delete a cloud account. `DELETE /api/cloud-account` Soft-deletes the cloud account. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `accountId` | query | string | yes | ID of the cloud account. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `accountId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/cloud-account?accountId=ca_1 ``` Example response: ``` {} ``` ## List step templates Source: https://www.jawsdeploy.net/rest-api/step-templates-list | Section: Step Templates List the step templates in a workspace. `GET /api/step-template` Returns every step template visible to the workspace, including supported run modes and script language. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/step-template?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "stepTemplates": [ { "stepTemplateId": "st_1", "name": "Deploy NuGet package", "scriptLanguage": "powershell", "supportedRunModes": ["TargetMachine"] } ] } ``` ## Get step template details Source: https://www.jawsdeploy.net/rest-api/step-templates-details | Section: Step Templates Retrieve one complete step template by ID or name. `GET /api/step-template/details` Returns the complete definition of one step template in the requested workspace, including the script body and the JSON-encoded property schema used when adding the template to a project. Provide exactly one lookup value: `stepTemplateId`, or `name`. Use the ID when names are duplicated. Names containing spaces or punctuation must be URL encoded. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace that owns the template. | | `stepTemplateId` | query | string | no | Step template ID from [List step templates](https://www.jawsdeploy.net/rest-api/step-templates-list). Required when `name` is omitted. | | `name` | query | string | no | Exact step template name. Required when `stepTemplateId` is omitted; URL encode the value. | #### What comes back in propertiesRaw `propertiesRaw` is the stored schema rather than the text that was sent to [Create a step template](https://www.jawsdeploy.net/rest-api/step-templates-create). It is normalised on save, so every member of a property is present here, including the ones left unset, and any member that is not part of the schema is gone. A template that was written with a `DefaultValue` field comes back without it. The typed fields carry each property's starting value - `ValueText`, `ValueBoolean`, `ValueNumber` or `ValueObject`, depending on the control type. A step added from the template starts from those. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`; neither or both lookup values were supplied; or the template was not found. | | 401 | Missing or invalid Basic auth credentials, or the service account cannot manage step templates in this workspace. | | 409 | The supplied name is not unique in the workspace. Retry with `stepTemplateId`. | Example request: ``` GET /api/step-template/details?workspaceId=ws_abc&stepTemplateId=st_2 HTTP/1.1 Authorization: Basic # Or look up by an URL-encoded name: GET /api/step-template/details?workspaceId=ws_abc&name=Restart%20Windows%20service HTTP/1.1 Authorization: Basic ``` Example response: ``` { "stepTemplateId": "st_2", "workspaceId": "ws_abc", "name": "Restart Windows service", "description": "Stops and restarts a Windows service.", "version": 3, "scriptLanguage": "powershell", "supportedRunModes": ["TargetMachine"], "script": "Restart-Service $ServiceName", "propertiesRaw": "[{\"Id\":\"ServiceName\",\"Name\":\"Service name\",\"Description\":null,\"ControlType\":\"SingleLineText\",\"ControlTypeOptions\":null,\"ControlValues\":null,\"DependsOn\":null,\"IsBound\":false,\"ValueObject\":null,\"ValueText\":\"Spooler\",\"ValueNumber\":0.0,\"ValueBoolean\":false,\"Properties\":[]}]" } ``` ## Create a step template Source: https://www.jawsdeploy.net/rest-api/step-templates-create | Section: Step Templates Define a new reusable step template. `POST /api/step-template` Creates a step template. `propertiesRaw` is a JSON-encoded array of property definitions - the inputs a project step built from this template asks for, and the values it starts with. See below for the shape. Step templates are the building blocks projects pick from when defining steps. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Template name. | | `description` | body | string | no | Optional description. | | `supportedRunModes` | body | array | no | See below. | | `script` | body | string | yes | Body of the script. | | `scriptLanguage` | body | string | no | `powershell` (default, PowerShell 7), `powershell5` (Windows PowerShell 5.1) or `python` (Python 3). | | `propertiesRaw` | body | string | no | JSON-encoded array of property definitions. Defaults to `"[]"`. See below. | #### Supported run modes Each `supportedRunModes` entry must be `TargetMachine`, `Worker`, or `WorkerToCloudTargets`. Values that cannot be parsed are ignored; if no valid entries remain, the request returns `400`. #### Property definitions `propertiesRaw` is a string holding a JSON array, so it is JSON-encoded twice inside the request body. Each element describes one input on the form shown when the template is added to a project. - `Id` - identifier, unique among its siblings. The script reads the value as `STEP.`, or `STEP..` for a property nested in a group. - `Name` - the label shown above the control. - `Description` - optional help text under it. - `ControlType` - one of `SingleLineText`, `MultiLineText`, `SecureString`, `Checkbox`, `Number`, `DropDownList`, `ScriptEditor`, `JsonEditor`, `PackageSelector`, `CloudAccountSelector`, `PropertyGroup`. - `ControlTypeOptions` - settings the control needs. `ScriptEditor` takes `Language` (`powershell`, `powershell5`, `python` or `json`), `CloudAccountSelector` takes `CloudType`. - `ControlValues` - the options offered by a `DropDownList`, as `[{"Name": "shown in the list", "Value": "read by the script"}]`. - `DependsOn` - show this property only while another one holds a value, as `{"ControlId": "OtherProperty", "Operator": "Equals", "Value": "yes"}`. `Operator` is `Equals` or `NotEquals`, and `ControlId` is the other property's `Id`, written `.` when it sits inside a group. - `Properties` - the nested array a `PropertyGroup` contains. Groups hold other properties and no value of their own. #### Initial property values There is no `DefaultValue` field. A starting value goes in the typed field that matches the control. | Control type | Field to set | | --- | --- | | `SingleLineText`, `MultiLineText`, `SecureString`, `ScriptEditor`, `JsonEditor`, `DropDownList`, `CloudAccountSelector` | `ValueText` | | `Checkbox` | `ValueBoolean` | | `Number` | `ValueNumber` | | `PackageSelector` | `ValueObject` | A step added from the template starts with those values, and whoever adds the step can change them afterwards. `DropDownList` is the exception - a new step always takes the first `ControlValues` entry, whatever `ValueText` says. Setting `IsBound` to `true` replaces the control with a variable expression editor. `ValueText` then holds an expression such as `#{ServiceName}` that is resolved at deploy time, whatever the control type is. Any other member of a property object is discarded when the template is saved. The request still returns `200`, and the discarded field is simply absent from the schema that [Get step template details](https://www.jawsdeploy.net/rest-api/step-templates-details) returns. `DefaultValue` is the one to watch for - it reads as though it should work, and a template carrying it produces steps whose inputs are empty. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`, invalid `scriptLanguage`, or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/step-template { "workspaceId": "ws_abc", "name": "Restart Windows service", "scriptLanguage": "powershell", "script": "Restart-Service $ServiceName", "propertiesRaw": "[{\"Id\":\"ServiceName\",\"Name\":\"Service name\",\"ControlType\":\"SingleLineText\",\"ValueText\":\"Spooler\"}]" } ``` Example response: ``` { "stepTemplateId": "st_2" } ``` ## Update a step template Source: https://www.jawsdeploy.net/rest-api/step-templates-update | Section: Step Templates Update a step template's name, script, properties, or run modes. `PUT /api/step-template` Updates a step template. Pass only the fields to change. Existing project steps using this template keep their current property values until the next edit. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `stepTemplateId` | body | string | yes | ID of the step template. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `supportedRunModes` | body | array | no | See below. | | `script` | body | string | no | New script body. | | `scriptLanguage` | body | string | no | New script language. | | `propertiesRaw` | body | string | no | The replacement property schema, JSON-encoded. Replaces the whole array - see below. | #### Supported run modes Each `supportedRunModes` entry must be `TargetMachine`, `Worker`, or `WorkerToCloudTargets`. Values that cannot be parsed are ignored; if no valid entries remain, the request returns `400`. #### Property definitions `propertiesRaw` takes the same array [Create a step template](https://www.jawsdeploy.net/rest-api/step-templates-create) describes, including the typed `ValueText`, `ValueBoolean`, `ValueNumber` and `ValueObject` fields that carry a property's starting value. There is no `DefaultValue` field, and a member that is not part of the schema is dropped without an error. Sending it replaces the property array outright rather than merging into it, so send the complete schema. Leave it out and the template keeps the properties it has. Existing steps are not touched by this call. A step runs the template version it is pinned to, and it picks up a new schema only when something pulls the template onto it - see [Update project step](https://www.jawsdeploy.net/rest-api/project-step-update). ### Errors | Status | Meaning | |---|---| | 400 | Invalid `stepTemplateId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/step-template { "stepTemplateId": "st_2", "scriptLanguage": "powershell" } ``` Example response: ``` {} ``` ## Delete a step template Source: https://www.jawsdeploy.net/rest-api/step-templates-delete | Section: Step Templates Soft-delete a step template. `DELETE /api/step-template` Soft-deletes the step template. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `stepTemplateId` | query | string | yes | ID of the step template. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `stepTemplateId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/step-template?stepTemplateId=st_2 ``` Example response: ``` {} ``` ## List script modules Source: https://www.jawsdeploy.net/rest-api/script-modules-list | Section: Script Modules List the script modules in a workspace. `GET /api/script-module` Returns the workspace's script modules: name, description, and script language. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/script-module?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "scriptModules": [ { "scriptModuleId": "sm_1", "name": "AuthHelpers", "scriptLanguage": "powershell" } ] } ``` ## Create a script module Source: https://www.jawsdeploy.net/rest-api/script-modules-create | Section: Script Modules Add a new script module to a workspace. `POST /api/script-module` Creates a script module containing PowerShell (or other supported) functions that projects can import into steps. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Module name. | | `description` | body | string | no | Optional description. | | `script` | body | string | yes | Module script body. | | `scriptLanguage` | body | string | no | `powershell` (default), etc. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/script-module { "workspaceId": "ws_abc", "name": "AuthHelpers", "script": "function Get-Token { ... }" } ``` Example response: ``` { "scriptModuleId": "sm_1" } ``` ## Update a script module Source: https://www.jawsdeploy.net/rest-api/script-modules-update | Section: Script Modules Update a script module's body, language, or metadata. `PUT /api/script-module` Updates the script module. Pass only the fields to change. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `scriptModuleId` | body | string | yes | ID of the script module. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `script` | body | string | no | New script body. | | `scriptLanguage` | body | string | no | New script language. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `scriptModuleId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/script-module { "scriptModuleId": "sm_1", "script": "function Get-Token { ... new body ... }" } ``` Example response: ``` {} ``` ## Delete a script module Source: https://www.jawsdeploy.net/rest-api/script-modules-delete | Section: Script Modules Soft-delete a script module. `DELETE /api/script-module` Soft-deletes the script module. Projects that imported it stop seeing it on the next deployment. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `scriptModuleId` | query | string | yes | ID of the script module. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `scriptModuleId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/script-module?scriptModuleId=sm_1 ``` Example response: ``` {} ``` ## List tags Source: https://www.jawsdeploy.net/rest-api/tags-list | Section: Tags List the tags defined in a workspace. `GET /api/tag` Tags are reusable labels applied to machines, cloud targets, project variable values, and steps for scope filtering. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/tag?workspaceId=ws_abc ``` Example response: ``` { "tags": [ { "tagId": "tag_1", "tagName": "us-east" } ] } ``` ## Create a tag Source: https://www.jawsdeploy.net/rest-api/tags-create | Section: Tags Create a new tag in a workspace. `POST /api/tag` Creates a tag the workspace can apply to deployment targets and variable values. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `tagName` | body | string | yes | Tag name. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/tag { "workspaceId": "ws_abc", "tagName": "us-east" } ``` Example response: ``` { "tagId": "tag_1" } ``` ## Update a tag Source: https://www.jawsdeploy.net/rest-api/tags-update | Section: Tags Rename a tag. `PUT /api/tag` Updates a tag's name. Renaming a tag does not break existing assignments. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `tagId` | body | string | yes | ID of the tag. | | `tagName` | body | string | no | New tag name. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `tagId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/tag { "tagId": "tag_1", "tagName": "us-east-1" } ``` Example response: ``` {} ``` ## Delete a tag Source: https://www.jawsdeploy.net/rest-api/tags-delete | Section: Tags Delete a tag. `DELETE /api/tag` Deletes a tag. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `tagId` | query | string | yes | ID of the tag. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `tagId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/tag?tagId=tag_1 ``` Example response: ``` {} ``` ## List workspace variables Source: https://www.jawsdeploy.net/rest-api/workspace-variables-list | Section: Workspace Variables List the variables defined at the workspace level. `GET /api/workspace-variable` Returns every workspace-level variable with its values and scope filters. Use the `/api/project/workspace-variable` endpoints to import these into projects. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | query | string | yes | ID of the workspace. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` GET /api/workspace-variable?workspaceId=ws_abc ``` Example response: ``` { "workspaceId": "ws_abc", "variables": [ { "variableId": "wv_1", "name": "ConnectionString", "variableType": "Secret", "values": [ { "variableValueId": "wvv_1", "theValue": "...", "environmentsFilter": ["env_prod"] } ] } ] } ``` ## Create a workspace variable Source: https://www.jawsdeploy.net/rest-api/workspace-variables-create | Section: Workspace Variables Create a new variable at the workspace level. `POST /api/workspace-variable` Creates a workspace variable. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `workspaceId` | body | string | yes | ID of the workspace. | | `name` | body | string | yes | Variable name. | | `description` | body | string | no | Optional description. | | `variableType` | body | string | no | See below. | #### Variable types `variableType` accepts `Text`, `Secret`, `Script`, `Number`, `Boolean`, `Date`, `Map`, and `Json`. `Text` is used when the field is omitted. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `workspaceId`, invalid `variableType`, or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/workspace-variable { "workspaceId": "ws_abc", "name": "ConnectionString", "variableType": "Secret" } ``` Example response: ``` { "variableId": "wv_1" } ``` ## Update a workspace variable Source: https://www.jawsdeploy.net/rest-api/workspace-variables-update | Section: Workspace Variables Update workspace variable metadata. `PUT /api/workspace-variable` Updates the variable definition. Pass only the fields to change. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableId` | body | string | yes | ID of the workspace variable. | | `name` | body | string | no | New name. | | `description` | body | string | no | New description. | | `variableType` | body | string | no | See below. | #### Variable types `variableType` accepts `Text`, `Secret`, `Script`, `Number`, `Boolean`, `Date`, `Map`, and `Json`. Omit the field to leave the type unchanged. ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableId` or validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/workspace-variable { "variableId": "wv_1", "description": "Primary connection string" } ``` Example response: ``` {} ``` ## Delete a workspace variable Source: https://www.jawsdeploy.net/rest-api/workspace-variables-delete | Section: Workspace Variables Soft-delete a workspace variable. `DELETE /api/workspace-variable` Soft-deletes the workspace variable. Projects that imported it lose access on the next deployment. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableId` | query | string | yes | ID of the workspace variable. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/workspace-variable?variableId=wv_1 ``` Example response: ``` {} ``` ## Add a workspace variable value Source: https://www.jawsdeploy.net/rest-api/workspace-variable-value-create | Section: Workspace Variables Add a scoped value to a workspace variable. `POST /api/workspace-variable/value` Adds a new value with optional environment / machine / cloud target / tag filters - same shape as project variable values. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableId` | body | string | yes | ID of the workspace variable. | | `theValue` | body | string | no | Value content. | | `environmentsFilter` | body | array | no | Restrict to these environments. | | `machineFilter` | body | array | no | Restrict to these machines. | | `cloudTargetFilter` | body | array | no | Restrict to these cloud targets. | | `tagFilter` | body | array | no | Tag-set filter. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableId` or filter validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` POST /api/workspace-variable/value { "variableId": "wv_1", "theValue": "Server=prod;...", "environmentsFilter": ["env_prod"] } ``` Example response: ``` { "variableValueId": "wvv_1" } ``` ## Update a workspace variable value Source: https://www.jawsdeploy.net/rest-api/workspace-variable-value-update | Section: Workspace Variables Update a scoped value on a workspace variable. `PUT /api/workspace-variable/value` Updates the value content and/or its filters. Pass only the fields to change. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableValueId` | body | string | yes | ID of the value. | | `theValue` | body | string | no | New value content. | | `environmentsFilter` | body | array | no | Replacement environments filter. | | `machineFilter` | body | array | no | Replacement machine filter. | | `cloudTargetFilter` | body | array | no | Replacement cloud target filter. | | `tagFilter` | body | array | no | Replacement tag filter. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableValueId` or filter validation failure. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` PUT /api/workspace-variable/value { "variableValueId": "wvv_1", "theValue": "Server=prod;..." } ``` Example response: ``` {} ``` ## Delete a workspace variable value Source: https://www.jawsdeploy.net/rest-api/workspace-variable-value-delete | Section: Workspace Variables Soft-delete a workspace variable value. `DELETE /api/workspace-variable/value` Soft-deletes the value. ### Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | `variableValueId` | query | string | yes | ID of the value. | ### Errors | Status | Meaning | |---|---| | 400 | Invalid `variableValueId`. | | 401 | Missing or invalid Basic auth credentials, or the service account lacks the required role. | Example request: ``` DELETE /api/workspace-variable/value?variableValueId=wvv_1 ``` Example response: ``` {} ``` # Features ## Deployment Pipelines Source: https://www.jawsdeploy.net/features/deployment-pipelines Define the ordered work a release has to do, attach it to environments and targets, and run the same flow whether you are pushing to one box or a fleet. ### What a Jaws Deploy pipeline actually is A pipeline in Jaws Deploy is a **project's deployment process**: an ordered list of steps attached to environments and targets. It is not a YAML file in a repo, and it is not a free-form script. It is a structured deployment definition that the platform owns, versions through releases, and executes against the targets it knows about. The shape matters. CI pipelines describe **how to build**. Jaws Deploy pipelines describe **how to deploy a thing that is already built** to a specific environment, with the right configuration, in the right order, with logs and history attached. > **// Mental model - Build once. Deploy many times. Re-execute the same plan.** > > Your CI tool produces an artifact and hands it off. Jaws Deploy locks the deployment plan into a release and runs that same plan every time the release is deployed - to dev, to staging, to production, to that one weird customer environment - without rewriting the pipeline for each. ### How a pipeline executes Every step has a place in the order, a target scope, and either a built-in template or a custom script. When you deploy a release, Jaws Deploy walks the steps top-to-bottom and runs each one against the targets that match its scope. If a step targets multiple machines, Jaws Deploy runs the work on each of them in parallel, then waits for the slowest one before moving on. That gives you a predictable per-environment shape: fan out across a tier, gate on the slowest target, continue. ### The four kinds of steps you actually write - **Package deployments**: Extract a package onto a target, run pre/post hooks, apply config transforms, and clean up old versions. - **PowerShell scripts**: Run inline or modular PowerShell against targets with full access to scoped variables and script modules. - **Windows service & IIS**: Install, restart, or reconfigure Windows services and IIS sites without writing the plumbing every time. - **Custom step templates**: Encapsulate a pattern once - input fields, defaults, validation - and reuse it across projects and teams. ### Built-in templates vs. your own steps Most teams start by chaining built-in step templates: deploy a package, run a script, swap an IIS binding. That covers the boring 80%. The interesting 20% - your domain-specific deployment dance - usually lives in either a custom step template (so it gets a UI form) or a script module (so it gets shared as a function library). The rule of thumb evaluators tend to land on: ### Templates, scripts, modules - pick the right tool - Use a **built-in template** when the platform already knows the shape (package, service, IIS, etc.). - Promote repeated PowerShell into a **custom step template** the moment two projects need the same form. - Use a **script module** for pure functions that several scripts call - it stays out of the deployment-step UI. - Drop down to **inline PowerShell** only for one-off, project-specific glue. If it survives a sprint, refactor it. ### A step is just a function with structured inputs Steps receive scoped variables as parameters and write to the live deployment log. No special framework, no DSL. ``` param( [string]$ConnectionString, # resolved from Variables for this env+target [string]$ReleaseVersion # provided by Jaws Deploy ) Write-Host "Migrating database to $ReleaseVersion ..." & "$PSScriptRoot\tools\migrate.exe" ` --connection $ConnectionString ` --target-version $ReleaseVersion if ($LASTEXITCODE -ne 0) { throw "Migration failed with exit code $LASTEXITCODE" } ``` > **// Sharp edge - Order is policy, not decoration.** > > Step order in a Jaws Deploy pipeline is the deployment policy. Putting the database migration before the package extract or after the service restart will change what 'works' looks like during a partial failure. Treat re-ordering steps with the same care as touching a deployment script. ### Scoping a step to targets and environments Steps are not just "run this everywhere." Each step has a scope: which environments it applies to, which target roles, which tags. That is how a single pipeline serves multiple environments without forking. A realistic project might have one pipeline where the migration step is scoped to a `role:db` tag, the package deploy step is scoped to `role:app`, and the cache-warmup step only runs in Production. Same pipeline. Different work in different places. **Build and test the artifact** Compile, unit-test, package. CI is good at this. Keep it there. Stop teaching Jenkins how to ssh into production. **Run the deployment plan** Take the artifact, resolve variables, walk steps in order, fan out across targets, and stream logs back. That is the platform's whole job. ### Why this shape beats hand-rolled scripts A pile of CI scripts can deploy a release. We have all written one. The cost is not the first deployment - it is the tenth environment, the third client, the rollback at 2am when someone needs to know **which release went to which environment and what exactly it did**. A Jaws Deploy pipeline is structured enough that the platform can answer those questions for you. Every step is a known unit. Every run is a known release. Every change to the deployment process is a change to the project, not a silent edit in a YAML file nobody reviewed. ## Rolling Deployments Source: https://www.jawsdeploy.net/features/rolling-deployments Rolling deployments roll a whole run of steps across your fleet, not one step at a time - so a machine stops, deploys, starts and smoke-tests before any other machine is touched. Use rolling groups to move each machine through a complete sequence of steps before the next machine begins. Choose the machine order, send a canary first, control how many machines roll at once, and stop at the right failure boundary. The [rolling deployments guide](https://www.jawsdeploy.net/guides/rolling-deployments) walks through the setup. - One machine out of service at a time - Canary first, by tag priority - Grouping never changes a step's reach - Machine-by-step rollout summary - Three levels of failure control ### Keep one machine out of service at a time A **rolling group** keeps related steps together for each machine. For a sequence that stops the service, deploys the package, starts the service and runs a smoke test, `web01` completes all four steps before `web02` begins. The rolling window defaults to 1, keeping the rest of the fleet available while each machine is updated. Increase it for controlled batches without changing which machines each step targets. ``` Rolling steps Rolling group stop web01 web02 web03 web01 stop > deploy > start > smoke deploy web01 web02 web03 web02 stop > deploy > start > smoke start web01 web02 web03 web03 stop > deploy > start > smoke smoke web01 web02 web03 ``` ### Canary first, and nothing widens a step's reach Order machines by name, or by tag priority - a machine's rank is the index of the first tag it carries, and machines run in rank order and by machine name within a rank, with machines carrying none of the tags last. Turn on the barrier and every machine of one tag rank finishes before the next rank starts, so the canary goes alone all the way through the group before the fleet follows. Members keep their own machine filters. A group's machine set is the union of the machines its members target, and on each machine only the members that target it run - the rest are recorded as `not targeted`. Group membership changes a rollout's structure, never a step's reach: environment scoping is untouched, and deployment-level machine include/exclude still applies ahead of everything else. ### Know which machines are on which version The deployment preview shows the resolved machine order, the window, the barrier, and machine by machine exactly which steps will run where. The log tree runs Group to Machine to Step, with machines not yet reached visibly queued rather than missing. Every rollout ends in a machine-by-step summary - written even when the rollout stops part-way, which is exactly when machines are left on mixed versions. **Read next:** the [rolling deployments guide](https://www.jawsdeploy.net/guides/rolling-deployments) for the settings behind each of these, [Project Steps Explained](https://www.jawsdeploy.net/guides/project-steps-explained) for how steps target machines in the first place, and [Projects and Deployment Logic](https://www.jawsdeploy.net/guides/projects-and-deployment-logic) for where the deployment process sits. ``` Rollout summary - rolling group 'web-rollout' Machine stop-service deploy-package start-service smoke-test web01 ok ok ok ok web02 ok failed skipped skipped web03 not run not run not run not run ``` ## Environments & Targets Source: https://www.jawsdeploy.net/features/environments-targets Environments, machines, cloud targets, and tags are first-class objects - not strings inside a script. That is the difference between a pipeline that scales and one that gets copied for every new region. ### The vocabulary Four nouns do most of the work in Jaws Deploy infrastructure modeling: **Environment**, **Machine**, **Cloud Target**, and **Tag**. They map cleanly to what you already have in production - the trick is using them with intent instead of treating them as labels. ### The four building blocks - **Environment**: A logical deployment stage - Dev, Test, Staging, Production, Customer-A-Prod. Steps and variables scope to it. - **Machine**: A physical or virtual server running the Jaws Deploy Agent. The agent makes outbound calls; you do not open ports inward. - **Cloud Target**: An Azure Web App or similar service the platform talks to without a machine sitting underneath. Agentless by design. - **Tag**: A label attached to machines and cloud targets. Tags scope variables, scope steps, and let you target by role or region. ### Environments are policy, not folders A Jaws Deploy environment is more than a name. It is a **scope** that variables, steps, and lifecycles attach to. When a release is deployed to Production, the platform resolves every variable through the Production scope, runs every step that opted into Production, and writes the deployment to Production history. That is what makes "deploy this release to staging then to production" a sentence the platform actually understands, instead of a wish you encode into a shell script. > **// Avoid this antipattern - Don't create one environment per machine.** > > Treating each server as its own environment is the fastest way to lose the value of the model. Environments are stages of the release lifecycle. Machines are *where the work runs*. Tags are *how you group them*. Keep those three layers separate and the platform pulls its weight. ### Machines, agents, and how work actually reaches them A machine in Jaws Deploy is a server with the **Jaws Deploy Agent** installed. The agent connects outbound to the control plane and waits for work. That single design decision shapes a lot of evaluator concerns: ### Network and security implications - **No inbound ports.** The control plane never initiates a connection into your network. Useful when production lives behind a NAT or a hardened firewall. - **Same model on Windows and Linux.** The agent runs on both. Steps that should work on one usually work on the other, with the obvious caveats around path separators and shell. - **Survives intermittent connectivity.** If the agent loses the control plane briefly, it reconnects and resumes streaming logs. - **One agent per machine.** That is the unit. Don't try to share agents across machines or run two on one box - you will eventually regret it. ### Tags are how a single pipeline serves multiple roles Tags are pure strings, but you should treat them as schema. Pick a convention early. Examples that work well in the field: ``` role:app # application servers role:db # database hosts role:cache # redis / memcached region:eu-west # geographic placement tenant:acme # multi-tenant isolation size:large # capacity tier ``` ### Cloud targets: when there is no machine For Azure Web Apps - and increasingly for other managed services - there is no agent because there is no OS to install one on. Cloud targets cover that gap. The platform talks to the service's management API and deploys to it directly. The trade-off is real: you get less control over the execution environment, and some custom PowerShell tricks won't run. In exchange, you skip the agent lifecycle entirely. For a team that ships an Azure App Service plus a couple of supporting Windows boxes, mixing both models in one project is normal. **When you need an agent** Use machines for Windows Services, IIS sites, custom binaries, anything that needs to run **inside** the server. The agent is the bridge. **When you don't** Use cloud targets for managed services where the cloud provider runs the host. Less control, less to maintain, fewer moving parts. > **// Real-world layout - What a healthy infrastructure model looks like** > > Three environments (Dev, Staging, Production), each with a handful of tagged machines and one or two cloud targets. Tags `role:app`, `role:db`, `region:*`. Every variable scoped to either an environment, a tag, or a specific target. No project hardcodes a machine name. That is the shape that scales. ### Why this matters when you're evaluating Every deployment tool can deploy a single artifact to a single box. The interesting question is what happens when the count goes up - twelve services, three regions, two tenants. If your model treats each as its own snowflake, the platform stops paying for itself. Jaws Deploy's environment/machine/tag triple is deliberately small. There is no "Cluster", no "Region" object, no "Stack". A tag is a tag. An environment is a stage. A machine is a machine. The reduced surface area is the feature. ## Variables & Secrets Source: https://www.jawsdeploy.net/features/variables Store variables and secrets once, then resolve the right values for each environment, target, and step during deployment. Use scoped variables for connection strings, service settings, feature flags, and deployment-specific values without hard-coding them in CI. Scope each value as broadly as a workspace or as narrowly as a single step, and let steps hand computed values to each other through output variables. - Scoped from workspace down to a single step - Encrypted secret handling - Nested variable references - Output variables shared between steps ### Scope a value to exactly where it applies Define a variable once and attach one or more values, each scoped to where it should apply. When several scopes match the same variable, the most specific value wins: step beats target, target beats tag, tag beats environment, environment beats project, project beats workspace. - Workspace — shared defaults across every project - Project — values owned by a single project - Environment — Dev, Staging, and Production differ - Target & tag — per-machine or per-role values - Step — a value that applies to one deployment step ### Scope a value to a single Project Step A variable value can carry a step filter, pinning it to one or more steps in the deployment process. A step-scoped value is the most specific scope there is — when that step runs, its value overrides the environment-, target-, or tag-scoped value for the same variable. Use it when one step needs a different connection string, path, or feature flag than the rest of the process, without inventing a new variable name. ### Output variables: pass values between steps Steps don't only consume variables — they can produce them. A script step records an output value that later steps in the same deployment read back by reference. Use it to hand a generated URL, a resource ID, or a computed version number downstream, with no database or temp file in between. Values can be scoped to the machine that produced them or shared globally across the deployment. ``` # PowerShell — record an output value Set-JawsOutputValue -Name "DeployedUrl" -Value $url # Python — the same, shared across the whole deployment import jaws jaws.set_output_value("DeployedUrl", url, scope="Global") # A later step reads it back by reference #{OUTPUT.Step.Deploy Web App.Global.DeployedUrl} ``` ## Variable Filters & Substitution Modes Source: https://www.jawsdeploy.net/features/variable-filters-and-modes Extended variable syntax adds transformations and conditional rendering. Workspace modes let existing deployments adopt it deliberately, without changing output on upgrade. Transform a variable directly inside its `#{...}` reference instead of adding one-off scripting around it. Project variables use `VAR.` and built-in deployment values use `CONTEXT.`; the unprefixed project-variable shorthand is available only during package file replacement. Convert case, trim text, replace patterns, format numbers and dates, encode values, escape configuration formats, and feed comparisons into conditional blocks. ### Filter, chain, and render conditionally Filters run from left to right, after nested variable references are resolved. Use `VAR.` for project variables and `CONTEXT.` for built-in deployment values. ``` #{VAR.SiteName | Trim | ToLower} #{VAR.ConnectionJson | JsonEscape} #{if CONTEXT.EnvironmentName == "Production"} logging.level=Warning #{else} logging.level=Debug #{/if} ``` ### Safe controls around more expressive templates - **18 built-in filters**: Case conversion, text manipulation, formatting, encoding, escaping, matching, and comparisons. - **Conditional blocks**: Use `if`, `unless`, and `else`, including nesting and filtered conditions. - **Compatibility modes**: Existing workspaces stay on Legacy; new workspaces default to Extended unless you choose otherwise. - **Unresolved-token policy**: Leave missing tokens silent, warn in deployment logs, or fail before the step runs. > **// Upgrade safely - Audit first, then opt in.** > > Existing workspaces are pinned to **Legacy**, so upgrading changes neither rendered output nor deployment logs. Switch unresolved tokens to **Warn**, clear genuine mistakes or escape literal text as `##{...}`, update agents that replace variables inside package files, then enable **Extended**. Move to **Strict** only when warnings are clean. The [Variable Filters & Substitution Modes guide](https://www.jawsdeploy.net/guides/variable-filters-and-modes) documents every filter, conditional rule, error case, compatibility guarantee, and rollout step. ## Releases & Promotions Source: https://www.jawsdeploy.net/features/releases-promotions A release is an immutable snapshot - packages, variables, deployment process, everything. Promotion is the act of running that same snapshot somewhere new. No rebuilds, no drift. ### What's actually in a release When you create a release in Jaws Deploy, the platform takes a **snapshot** of three things and freezes them together under a SemVer 2.0 version number: ### Three frozen pieces - **Packages**: The exact package versions selected at release time - .zip, .tar, .nupkg, whatever your steps consume. - **Variables**: Variable definitions as of release creation. Values still resolve per environment, but the variable *set* is frozen. - **Deployment process**: The ordered steps that were in the project at the time. Edits to the project later do not retroactively change old releases. > **// The single most useful invariant - Promotion does not rebuild. Ever.** > > When you promote release 2026.5.16 from Staging to Production, Jaws Deploy runs the *same* release: same packages, same step definitions, same variable schema. The only thing that changes is which environment-scoped values get resolved. That is what makes a Staging signoff actually mean something. ### SemVer 2.0, but with intent Releases are versioned using SemVer 2.0 - so `1.4.2`, `2.0.0-rc.1`, `2026.5.16+build.4218` all work. The platform doesn't *force* a versioning scheme on you, but the convention that earns its keep is **CI-generated version, deploy-time confirmation**. Your build job decides the version. Jaws Deploy takes it and promotes it. Humans don't type version strings. ### How a CI job typically creates a release Most teams call this from TeamCity, GitHub Actions, or a Jenkinsfile right after a successful build: ``` Connect-JawsDeploy ` -Url "https://app.jawsdeploy.net" ` -ApiKey $env:JAWS_API_KEY New-JawsDeployRelease ` -Workspace "default" ` -Project "checkout-service" ` -Version "2026.5.16.${env:BUILD_NUMBER}" ` -Packages @{ "Checkout.Web" = "2026.5.16" } ` -ReleaseNotes (Get-Content .\CHANGELOG.md -Raw) ``` ### Lifecycles: the path a release has to walk A **Lifecycle** defines the ordered phases a release must pass through. The default is the obvious one - Dev -> Staging -> Production - but lifecycles are flexible enough to model things like "smoke environment then a manual approval then prod" or "two canary regions in parallel before global rollout". Lifecycles attach to projects via **channels**. A channel says: "hotfix releases go through this shorter lifecycle that skips Dev." A normal release goes through the standard one. Same project, two paths. **Dev -> Staging -> Production** Every phase must succeed before the next is allowed. Reasonable safety net for the 95% case. **Staging -> Production** Skip Dev when the situation demands it. The lifecycle records that you skipped, so audit is preserved. > **// Why this beats "just redeploy from CI" - Rebuilds are the enemy of repeatability.** > > If you ship to Staging by running the build, and then ship to Production the next day by running the build *again*, you have shipped two different artifacts. Source might be identical. Dependencies, base images, transient toolchain bits - those drift in hours. A real release captures the artifact once and stops asking CI to reproduce it. ### Promotions in practice Once a release exists, promoting it is a verb. From the UI, the REST API, or the PowerShell SDK, you tell Jaws Deploy "deploy release X to environment Y". The platform validates the lifecycle ("is this environment the next allowed phase?"), resolves variables for that environment, runs the steps, and records the deployment. A single release can be deployed many times to the same environment - useful for re-runs after a fix, for canary rollbacks, or for that one customer who needs the same artifact deployed to a fresh tenant box. ### And what it does not - **Guarantees** the same packages, same steps, same variable schema as the original release. - **Guarantees** the lifecycle is respected unless you explicitly bypass it with a channel. - **Does not** guarantee environment state. If Staging and Production disagree about, say, the OS patch level, that is on you to detect. - **Does not** snapshot live external systems (databases, third-party APIs). Those drift independently. ### The mental model that makes evaluators relax Think of a release as a **plan you can re-execute**. The plan is fixed; the destination changes. That single property - the plan is fixed - is what lets a team say "this release went through Staging" and have it actually mean something to the auditor reading it six months later. ## Live Deployment Logs Source: https://www.jawsdeploy.net/features/live-deployment-logs Live logs are streamed from the agent, grouped by step and target, and survive the page reload. You see what is happening, where it is happening, while it happens. ### What "live" actually means The deployment view in Jaws Deploy is a streaming tree, not a tail of stdout. Each **step** is a collapsible node. Each **target the step runs on** is a child node. Log lines, warnings, and errors appear under the right node as they happen. Refresh the page and the tree is still there - the log is persisted on the server, not buffered in your browser. > **// Why a tree, not a tail - Twelve targets writing to one stream is unreadable.** > > When a step fans out to twelve machines and they all log at the same time, a flat stdout is useless. The tree lets you collapse the boring ones, focus on the failing one, and still see the overall shape of the deployment. It is the difference between debugging blind and debugging with a map. ### What you see, live, while it runs - **Step output**: Stdout/stderr from each step, attributed to the right target. Color-coded for warnings and errors. - **Per-target timing**: Elapsed time per target so you can spot the one slow box without a stopwatch. - **Step status**: Pending, running, finished, failed, skipped - at both the step level and per target, with error and warning counts rolled up to every node above a line. A step can finish and still carry errors, so read the counts too. - **Warnings & errors**: Surfaced separately, not buried in the log. Filter to see only what's broken. ### The bits that surprise people in a good way A few details that show up in evaluator demos and tend to land well: ### Stuff a hand-rolled deployment script will never give you - **The log survives a browser refresh.** It is server-side. You can hand the URL to a teammate at any point. - **Logs are addressable.** Every line has a permalink. Useful for incident write-ups and code review. - **Parallel execution is visible.** When a step runs on five machines at once, you see five lanes filling in, not one merged firehose. - **Warnings are first-class.** A non-fatal PowerShell warning is not lost in the noise - it shows up in the warnings sidebar and stays attached to the step. - **Targets that have not started yet are placeholders.** You can see what is *about* to happen, not just what already did. > **// Performance reality check - Logs are cheap to produce. They are not free.** > > If a step writes 5 MB of output per machine across twenty machines, that is 100 MB of log to stream, persist, and render. The platform handles it, but treat verbose `Write-Host` like you would treat `console.log` in production code: useful while debugging, regrettable as a permanent fixture. ### Tips for writing scripts that read well in the live view Treat log output as a UI. Future-you, reading this at 2am, is the user. ``` Write-Host "Step: applying configuration to checkout-service" Write-Host " target : $($env:JAWS_TargetName)" Write-Host " release : $($env:JAWS_ReleaseVersion)" try { Apply-Config -Path $configPath Write-Host " status : OK ($(($sw.Elapsed).TotalSeconds.ToString('0.0'))s)" } catch { Write-Warning " status : retrying after transient error: $_" Apply-Config -Path $configPath } ``` ### When the deployment fails - and it will A deployment that hit trouble looks the same in the live view as a clean one, with one important difference: the trouble is **scoped**. Jaws Deploy tells you which step reported the error, on which target, and shows you the log lines immediately above it. That sounds obvious until you have spent an afternoon scrolling through twelve thousand lines of CI output trying to find which of the eight servers actually rejected the package. With a tree, the red node has a name. Worth knowing when you read the tree: a step that reported errors still finishes, and so does the deployment. The counts on each node, not the overall status, are what tell you the work went wrong. **Agent side** The agent executes the step locally, captures stdout/stderr, and ships log frames to the control plane over the same outbound connection it uses for everything else. **Browser side** The live view subscribes to those frames. New lines paint in real time. The tree reshapes itself as steps start and finish. ### Logs and history are the same thing The live log does not vanish when the deployment finishes - it becomes the deployment record. The same view you watched at deploy time is the one you (or an auditor, or future-you) opens six months later from deployment history. There is no "runtime log" and "archived log" distinction; the live view is the archive. ## Deployment History & Insights Source: https://www.jawsdeploy.net/features/deployment-history Every release is a snapshot. Every deployment is a record. Six months later, you can still answer the question "what exactly went to production on that Tuesday?" without grepping CI logs. ### What's recorded Every deployment in Jaws Deploy writes a structured record. Not a log file in a folder - a queryable record. The record sticks around indefinitely unless you intentionally clean it up. ### What you can recover for any past deployment - **Package versions**: The exact artifact versions that went out. Not the source commit - the artifact. - **Variable snapshot**: The variables (with secrets redacted) as they were at deploy time. Not as they are now. - **Step-by-step log**: The same live log you watched, persisted in full, with timing for each step on each target. - **Timeline & duration**: Start time, end time, per-step duration, queue time. Find your slow steps quickly. > **// The audit question this answers - "Was the fix for CVE-2026-1234 deployed to Production before April 12?"** > > With deployment history, the answer is a search, not an archaeology dig. Filter deployments by project, environment, and date range. Look at the release version. Look at what was in it. Move on. The same question without history takes a half-day and an apologetic email. ### Insights you actually look at The history view is not just an archive - it surfaces patterns that are tedious to spot manually. Things teams notice once they have a couple of months of history: ### Patterns that change how you deploy - **Step duration drift.** The migration step that used to take 30 seconds now takes four minutes. The chart shows the slope; you decide whether to act. - **Failure clustering.** "Most deployment failures happen on Monday morning, on Step 7, on machines with `role:cache`." That is a real fix waiting to happen. - **Environment cycle time.** The lag between a Staging deploy and the corresponding Production deploy. If it is widening, your release cadence is decaying. - **Promotion gaps.** Releases that went to Staging but never reached Production. Some are intentional. The ones that are not are interesting. - **Rollback frequency.** Not as a shame metric - as a signal of test-coverage gaps in specific areas. ### When you want to feed history into your own tooling The same data the UI shows is available via the REST API. A common pattern is exporting last 90 days into a dashboard your SREs already watch. ``` GET /api/workspaces/default/deployments ?project=checkout-service &environment=production &from=2026-02-01 &to=2026-05-01 # returns: deployment id, release version, status, duration, # start/end time, step counts, error counts ``` > **// The thing nobody plans for - Deployment history is your forensic timeline.** > > When a production incident happens, the first question is "what changed?" If the answer lives in five places - CI, chat, ticket comments, deploy scripts, someone's memory - the incident is twice as long. If it lives in deployment history with timestamps and per-step status, the postmortem writes itself. **Records persist** Deployment records are not log files - they live in the database with the rest of the platform state. The default is forever; cleanup is something you do on purpose. **Secrets stay secret** The variable snapshot stored with a deployment redacts secret values the same way the live log does. History never becomes a credentials trove. ### A useful test If you cannot answer **"which release was last deployed to Production, and what was in it"** in under thirty seconds, your current setup is undercharging you for deployments. That single sentence is what deployment history exists to make trivial. Everything else - the timing charts, the failure clustering, the auditor-friendly export - is layered on top of that one capability. ## Package Feeds / Artifact Store Source: https://www.jawsdeploy.net/features/package-feeds Every workspace has a built-in NuGet feed. Push from CI, pull during deployment, version with releases. No second artifact server to babysit - unless you already have one, in which case Jaws Deploy talks to it. ### What's built in Every workspace in Jaws Deploy ships with a **NuGet-compatible package feed**. It accepts `.nupkg`, `.zip`, `.tar`, and `.tar.gz` files. You push artifacts to it from CI, reference them in deployment steps, and the version that ends up in a release is locked at release-creation time. The feed is not a separate product. There is no second service to install, configure, monitor, or upgrade. It is part of the workspace the same way variables and projects are. > **// Why this matters more than it sounds - One less thing in the deployment dependency chain.** > > A typical alternative stack is: CI builds an artifact, pushes it to Artifactory/Nexus/an S3 bucket, the deployment tool pulls it from there, and somebody owns the artifact server's uptime. Jaws Deploy collapses that into one hop. The artifact lives next to the release that uses it. ### Supported package formats - **.nupkg**: Native NuGet packages. The feed speaks the NuGet protocol, so any tool that pushes NuGet can push here. - **.zip**: Generic zip archives for non-.NET workloads. Steps that deploy zips extract them onto the target. - **.tar / .tar.gz**: For Linux deployments and anything that has been tarballed by tradition. - **External feeds**: Connect TeamCity, Azure DevOps, GitHub Packages, or Artifactory. Steps reference them like the built-in feed. ### The push-from-CI pattern The typical flow is: your CI job builds the artifact, packages it, and pushes it to the workspace feed. From there, anyone (or anything) that creates a release in that workspace can select that artifact version. CI doesn't need to know about deployments at all - it only needs an API key. ### NuGet-compatible push, no special SDK required Because the feed speaks NuGet, `nuget push` and `dotnet nuget push` work as-is. PowerShell SDK is just sugar. ``` # from any CI runner with the API key in scope nuget push Checkout.Web.2026.5.16.nupkg ` -Source https://app.jawsdeploy.net/nuget/v3/index.json ` -ApiKey $env:JAWS_API_KEY # or, with the PowerShell SDK Push-JawsDeployPackage ` -Path ./Checkout.Web.2026.5.16.nupkg ` -Workspace default ``` ### Versioning and the release link When you create a release, Jaws Deploy asks: "which package versions should this release include?" You pick `Checkout.Web 2026.5.16` and `Checkout.Migrations 2026.5.16`, and those exact versions are recorded on the release. From that moment on, the release is bound to those artifacts. Promoting the release to Production deploys those specific files. Re-running the release deploys those specific files. Six months later, those specific files. The link is what makes promotion meaningful. > **// What goes in a package vs. what goes in a variable - Artifacts are bytes. Variables are strings.** > > A useful rule: if something can be rebuilt deterministically from source, it belongs in a package. If something differs per environment and exists only at deploy time, it belongs in a variable. Mixing the two - environment-specific files inside the package - is how teams end up with a Production-only artifact that nobody else can reproduce. ### External feeds If you already have an artifact server you cannot or will not give up, Jaws Deploy connects to it as an **external feed**. The deployment step references the external feed instead of the built-in one, and the rest of the platform behaves identically: release locks the version, history records what was deployed, promotion deploys the locked version. The most common external feeds in the wild: ### Where teams typically already have artifacts - **TeamCity build artifacts** - reference TeamCity build numbers directly as package versions. - **Azure DevOps Artifacts** - private NuGet feeds, npm feeds, generic feeds. - **GitHub Packages** - especially for `.nupkg` published from GitHub Actions. - **Artifactory / Nexus** - for teams with a centralized binary store they cannot retire. - **Public nuget.org** - for shared infrastructure packages that genuinely belong upstream. **When to use it** When you do not already have an artifact server. When you want the artifact to live next to the deployment. When you don't want a second thing to monitor. **When to use it** When CI already publishes to a feed your org owns. When licensing or compliance pins you to a specific binary store. When you have a multi-tool ecosystem and Jaws Deploy is one consumer of many. ### A final small detail The feed enforces immutability per-version. Once `Checkout.Web 2026.5.16` is pushed, you cannot push a different `.nupkg` with the same version number. That sounds like a constraint, until the first time someone tries to "just rebuild and push the same version" the day after a Production release. Then it sounds like a feature. ## Regional Package Delivery Source: https://www.jawsdeploy.net/features/regional-package-delivery Keep package bytes in your region and off the long-haul path. Pin a workspace's package store to a location near your servers, and let agents download straight from your own feeds - so a team in Sydney or Seattle deploys at local speed while accounts, UI, and orchestration stay centrally managed. ### One product, one login - bytes that stay in your region. Jaws Deploy runs centrally, and for clicks and deploy orchestration that is exactly right: those are small, latency-light messages. The expensive part is moving large **package artifacts** back and forth across the world on every deployment. Regional delivery removes that round trip in two ways you can adopt independently - a regional package store for the built-in feed, and direct downloads from your own feeds - without splitting your account, your billing, or your data model. - Package bytes stay in your region - No second Jaws installation to run - Works for the built-in feed and your own feeds - Short-lived, single-item access links - Opt-in per workspace and per feed - Existing workspaces keep working unchanged ### Two levers, adopt either or both - **Regional package store**: Pin a workspace's **package store location** to a region. Packages pushed to the built-in Jaws feed are uploaded to and downloaded from that region's storage - the bytes never leave it. Chosen at creation, then locked. - **Direct feed downloads**: Flip **Allow direct downloads** on a private feed and agents pull packages straight from your TeamCity, NuGet, or artifact server - usually already next to them. Works for any workspace, regional or not. - **Same trust boundary**: Both ride the authentication your agents and CI already use. Access is short-lived, scoped to a single package or feed, and can only ever reach resources inside the requesting machine's own workspace. ### Where your targets are, not where the datacenter is. For teams in Australia, the US, or anywhere far from central infrastructure, regional delivery is both a performance win and a **data-residency** story: the package artifacts you deploy stay in the location you choose. You pick that location based on where the workspace's deployment targets live - and because stored packages physically reside there, the choice is fixed at workspace creation rather than a setting that could silently move data later. ### Pin a workspace to a region Set the package store location at creation with regionId (list options via GET /api/workspace/regions). Omit it for the default location. ``` POST /api/workspace Authorization: Basic Content-Type: application/json { "name": "Sydney", "regionId": "au" } ``` ### Let agents skip the middle entirely For packages that live in your own feed, the shortest path is the one that never touches Jaws infrastructure. - Agents download each package straight from your feed - often on the same LAN as the target - Jaws only resolves a tiny metadata descriptor; the bytes go feed to agent - Off by default for existing feeds, pre-selected for newly created ones - Each machine caches a version once, so your feed is not hammered on every deploy - No server-side fallback when it is on - by design, your feed is meant to be reachable from your agents > **Security - Credentials stay inside your workspace** > > Direct-download feed credentials and regional storage links are delivered only over the authenticated, HTTPS negotiate response, only for resources in the machine's own workspace, and are never logged or persisted beyond the download. It is your own credential handed to your own machine - the same trust boundary agents already operate in when they receive deployment secrets. > **Go deeper - Guides & reference** > > Everything you need to set up regional delivery and direct feed downloads. - [Start free](https://app.jawsdeploy.net/signup) - [Read the guide](https://www.jawsdeploy.net/guides/regional-package-delivery) ## Step Templates & Script Modules Source: https://www.jawsdeploy.net/features/step-templates-script-modules Step templates give a repeated deployment pattern a form, validation, and defaults. Script modules share helper functions across every script you write. Together, they are the DRY layer your CI scripts never had. ### Two different abstractions, often confused Jaws Deploy gives you two ways to share deployment logic. They look related, but they solve different problems and you will use both. **A reusable step with a UI** Encapsulate a deployment *action* - extract a package, configure a service, restart IIS - with structured inputs and validation. Used by selecting it in a project's deployment process. **Shared PowerShell functions** Pure code reuse. A library of functions that any deployment script can `import-module` and call. No UI, no inputs - just functions you don't want to redefine. ### Step templates: structured, reusable, validated A step template is a parameterized step you can drop into any project's deployment process. It has: ### Beyond "a script in a file" - **Typed inputs** - strings, multi-line text, secrets, references to packages, choices from a dropdown. - **Per-input validation** - required vs. optional, default values, help text, even sensitive-data marking. - **Versioning** - bump the template version when you change behavior; projects opt in to the new version. - **Permissions** - team members can use the template without seeing or editing its body. - **Discoverability** - templates show up in the step picker with their description. New team members find them. > **// The cost of skipping this - Twelve copies of the same script, slowly diverging.** > > Teams that don't use step templates end up with the same script pasted into a dozen projects, each one slightly modified for a project-specific quirk. When a bug surfaces, the fix has to be replayed across all of them - and there is always one nobody knows about. A template makes that scenario impossible by construction. ### What you author once, what every project gets The template defines inputs and a body. Projects pick the template, fill in the inputs, and get a validated step. ``` # Template: Deploy-WindowsService # # Inputs: # ServiceName (string, required) # PackageName (package reference, required) # StopTimeoutSec (number, default 30) # RunAsUser (string, optional, sensitive: true) # # Body (PowerShell): param($ServiceName, $PackagePath, $StopTimeoutSec, $RunAsUser) Stop-Service $ServiceName -Force -Timeout $StopTimeoutSec Expand-Archive -Path $PackagePath -DestinationPath "C:\Services\$ServiceName" -Force if ($RunAsUser) { sc.exe config $ServiceName obj= $RunAsUser } Start-Service $ServiceName ``` ### Script modules: shared PowerShell functions A script module is a regular PowerShell module the platform manages. You write functions, you publish the module, and any deployment step in any project can `Import-Module` it and call the functions. The rule of thumb: if you have written the same five-line helper in three scripts, it belongs in a module. ### Things that live well in a script module - **Auth helpers**: Functions that wrap acquiring tokens, signing requests, or rotating credentials so individual steps stay short. - **Database helpers**: `Invoke-Migration`, `Wait-DatabaseReady`, `Backup-BeforeChange` - the small functions every team rewrites. - **Notification utilities**: Post to Slack, send an email, page someone. One function. Called from many scripts. - **Feature flags**: Read a flag from a config store and return a typed value. Done once, used everywhere. > **// When to template vs. when to module - If it has a UI form, it's a template. If it's pure code, it's a module.** > > A useful test: does the next person to use this need to fill out fields, or just call a function? If they need to think about inputs, give them a template with structured inputs. If they are writing a script and want to *not* rewrite a function, ship them a module. ### Versioning, ownership, and the politics of shared code The usual trap with shared deployment code is the same as with any internal library: one team owns it, another team needs a feature, the owners are busy, the consumers fork it. A few practical decisions help. ### Lessons from teams that did this and lived - **Pick owners.** Templates and modules need a maintainer. "Everyone" means "no-one". - **Version on change.** Bump the template version whenever behavior changes. Don't silently mutate. - **Deprecate, don't delete.** Keep old versions usable while consumers migrate. - **Document inputs.** The help text on each template input is what the consumer sees. Treat it like API docs. - **Treat shared code like product code.** It deserves PR review, tests, and a changelog. ### The pay-off, six months in A team that has invested in a small set of well-named step templates and one or two reliable script modules ends up with deployment processes that read like sentences. The deployment process for a new service is mostly "select template, fill three fields, select template, fill two fields." The interesting custom code lives in modules where it is reused. That is the long-term shape Jaws Deploy is built for - and it is the part that pays back the hours you spend setting it up in the first place. ## PowerShell & Python Scripting Source: https://www.jawsdeploy.net/features/scripting-powershell-python PowerShell 7, Windows PowerShell 5.1, and Python 3 are all first-class script runtimes. Jaws provisions and caches the right runtime on every target for you — no manual installs, and offline provisioning for air-gapped machines. ### One deployment context, three runtimes. Not every deployment step belongs in PowerShell — and not every team writes it. Every script step in Jaws runs under a managed runtime, and you pick the language per step: **PowerShell 7**, **Windows PowerShell 5.1**, or **Python 3**. Whichever you choose, the step receives the same deployment context. It can read resolved variables and packages, emit output variables for later steps, and streams into the deployment log with the same step-level and target-level visibility. You can mix PowerShell and Python steps in a single deployment process. - PowerShell 7 — the cross-platform default - Windows PowerShell 5.1 for legacy Windows steps - Python 3 as a first-class runtime - Managed runtimes provisioned and cached per target - Offline provisioning for air-gapped targets - Output variables shared across PowerShell and Python ### Pick the runtime per step - **PowerShell 7**: The cross-platform default. Reach the deployment context through the `$Jaws` object and emit results with `Set-JawsOutputValue`. - **Windows PowerShell 5.1**: For steps that depend on Windows-only modules or in-box tooling. Same `$Jaws` context and `Set-JawsOutputValue` functions. - **Python 3**: Write deployment logic in Python. `import jaws` gives you the same context PowerShell reaches through `$Jaws` — no extra install on your targets. ### Emit an output value from PowerShell Read a resolved variable from `$Jaws`, then hand a value to later steps. `-Scope` defaults to `Machine`; pass `Global` to share it across the deployment. ``` # PowerShell 7 or Windows PowerShell 5.1 $conn = $Jaws.Parameters["ConnectionString"].Value # Share a computed value with every later step Set-JawsOutputValue -Name "DeployedUrl" -Value "https://$conn/health" -Scope Global ``` ### The same step in Python `import jaws` exposes `jaws.parameters`, `jaws.packages`, and `jaws.set_output_value()` — the direct counterparts of the PowerShell context. ``` import jaws conn = jaws.parameters.get("ConnectionString") # Share a computed value with every later step jaws.set_output_value("DeployedUrl", f"https://{conn}/health", scope="Global") ``` ### Shared script modules, per language Helper code copied across script steps belongs in a **script module** — a workspace-level library any script step can import. Modules are written for a specific runtime: PowerShell steps import PowerShell modules, Python steps import Python modules. Centralise connection helpers, retry logic, and logging wrappers once instead of pasting them into every step. See [Step Templates & Script Modules](https://www.jawsdeploy.net/features/step-templates-script-modules) and the [Custom Script Modules guide](https://www.jawsdeploy.net/guides/custom-script-modules). ### Nothing to install on your targets The agent provisions and caches the runtime each script step needs, so scripts behave the same on every machine. - Runtime provisioned and cached per target on first use - Subsequent deployments reuse the cache — no download per step - Air-gapped targets provision from a local archive, fully offline - Warm the runtime at agent startup so the first deploy never waits > **Go deeper - Guides for both runtimes** > > Detailed walkthroughs of writing steps, sharing modules, and passing values between them. - [Start free](https://app.jawsdeploy.net/signup) - [Read the Python guide](https://www.jawsdeploy.net/guides/python-script-steps) ## MCP Server Source: https://www.jawsdeploy.net/features/mcp Connect Claude Code, Cursor, Codex, or any Model Context Protocol client to Jaws — using the AI subscription you already have. No extra fees. No model lock-in. No new vendor to evaluate. ### Your AI assistant, your deployments, your bill. Jaws ships with a built-in Model Context Protocol server. Point Claude Code, Claude Desktop, Cursor, Codex Desktop, Codex CLI — or anything else that speaks MCP — at your Jaws workspace, and the AI can create projects, assemble deployment pipelines from step templates, read deploy logs, author new templates, and set variables on your behalf. You pay your AI provider once. We don't charge per token, we don't proxy your model calls, and we don't tie you to a specific vendor. If you switch from Claude Code to Cursor next month, your Jaws setup keeps working. ### What you can do today - **Build deployment pipelines from a description**: Tell your AI what you need to deploy and how. It creates the project, adds the right steps from your template library, and fills in each step's properties. Everything lands in Jaws ready to review. - **Diagnose failed deploys**: Hand your AI a deployment ID. It reads the logs (optionally errors-only), the rendered script, and the step properties, and explains in plain English what broke and how to fix it. - **Manage variables in bulk**: Paste a config dump or environment definition, ask the AI to set the corresponding Jaws variables, and watch it happen. Secret-typed variables are intentionally blocked — you still set those by hand. - **Navigate and organise by conversation**: List my workspaces. Move all staging projects into a folder called Staging. Lookups and reorganisation that used to take six clicks become one sentence. ### A three-step setup 1. **Create a service account** in your Jaws workspace under **Settings → Service Accounts**. Generate an API key. 2. **Add the Jaws server to your AI client's MCP config.** Copy-pasteable snippets for each major client are in the [quickstart guide](https://www.jawsdeploy.net/guides/mcp-quickstart). 3. **Start asking questions.** Your AI now sees your workspaces, projects, step templates, variables, and deployment logs. ### Why this approach **We could've charged you per token.** Most deploy tools that ship AI features quietly become token resellers. They wrap a model provider, mark up the inference, and bake it into a new pricing tier. That's fine for them and bad for you — your costs scale with conversations, you're locked into whichever model they chose, and your spend on AI is now split across two bills. **We chose MCP instead.** By exposing Jaws as an MCP server, we let your existing AI subscription do the work. You bring your Claude, Cursor, or Codex plan. We bring the deployment surface. No inference markup. No new vendor to evaluate. When models get cheaper and better — which they will — the savings go straight to you. ### Built so the AI can't break things on its own - **No deploy triggers without confirmation.** Triggering a deployment is split into `preview_deploy` (dry run) and `trigger_deploy` (consumes a short-lived token). Your AI cannot deploy without you seeing a preview first. - **No secrets in transcripts.** Secret-typed variable values are masked when read and rejected when written. Secrets are set by hand in the Jaws UI — they never travel through an AI conversation. - **Every call is permission-checked.** Tool calls go through the same `WorkspaceGuard` as the REST API. A service account that can't see a workspace can't see it via MCP either. - **Auditable.** Every MCP call is a regular HTTP request to your Jaws hub — shows up in your existing access logs. - [Read the quickstart](https://www.jawsdeploy.net/guides/mcp-quickstart) - [Start free trial](https://app.jawsdeploy.net/signup) # Platform ## Integrations Source: https://www.jawsdeploy.net/platform/integrations Connect any CI system to release orchestration without rewriting build pipelines. Jaws Deploy integrates inbound via API and PowerShell SDK — and reaches outward from deploy steps using PowerShell (v.5 and v.7). ### Keep your CI. Add deployment. Jaws Deploy does not ask you to replace TeamCity, GitHub Actions, GitLab CI, Jenkins, or Azure DevOps. Build pipelines are good at compiling, testing, and packaging — that work stays where it is. What changes is what happens after the artifact is ready: release creation, environment promotion, variable resolution, target execution, and deployment history all move to a system built for that purpose. **CI hands off the artifact** When a build passes, trigger Jaws Deploy to create a release and start the deployment. Use the TeamCity plugin, a PowerShell SDK call, or a direct REST API request — whichever fits the build tool already running the pipeline. **Deploy steps call anything** Jaws Deploy runs PowerShell 5 and PowerShell 7 natively inside deployment steps. Deploy logic can call REST APIs, send notifications, update ITSM tickets, run health checks, or trigger downstream systems — all as part of the release flow. - **TeamCity**: A native build step creates releases and triggers deployments directly from a TeamCity pipeline without custom scripting. - **GitHub Actions, GitLab CI, Jenkins**: Trigger Jaws Deploy from any CI system that can run a shell command or make an HTTP call. The REST API and PowerShell SDK cover both. - **PowerShell SDK**: The PowerShell SDK wraps the full Jaws Deploy API. Use it in build steps, automation scripts, or anywhere PowerShell runs — Windows or Linux. - **REST API**: Every Jaws Deploy action is available over REST. Create releases, trigger deployments, query history, and manage environments from any language or toolchain. > **PowerShell in deploy steps - Outbound integration is just PowerShell.** > > Deploy steps in Jaws Deploy run PS5 or PS7. That makes outbound integration straightforward — call an API, update a ticket, notify a channel, run a health check. No plugin to install, no integration layer to maintain. Just PowerShell code that runs as part of your deployment. ### Explore further - [REST API docs](https://www.jawsdeploy.net/rest-api) - [Jaws Deploy Agents](https://www.jawsdeploy.net/platform/agents) - [PowerShell SDK](https://github.com/JawsDeploy/powershell-sdk) - [Python SDK](https://github.com/JawsDeploy/python-sdk) ## Jaws Deploy Agents Source: https://www.jawsdeploy.net/platform/agents A lightweight service that runs on your servers and handles deployment execution — without opening a single inbound port. Agents work with [Jaws Deploy Cloud](https://www.jawsdeploy.net/platform/cloud) and [Jaws Deploy Stack](https://www.jawsdeploy.net/platform/stack). ### What agents do Agents are the bridge between Jaws Deploy and the machines where deployment work actually runs. They receive deployment tasks from the Jaws Deploy server, execute steps against local targets, stream logs back in real time, and report completion status — all without requiring the Jaws Deploy server to reach directly into your infrastructure. ### Your servers stay behind the firewall The security model is the most important thing to understand about agents. The agent initiates an outbound connection to the Jaws Deploy server using a secure, persistent WebSocket channel — your servers do not need to accept any inbound connections. No firewall rules to open. No VPN tunnels to maintain. No exposed ports. This means the agent model works naturally with corporate security policies, DMZ layouts, on-premises networks, and private cloud environments where inbound access to production servers is prohibited or impractical. - **Outbound-only connection**: The agent opens a secure WebSocket channel to the Jaws Deploy server. Your infrastructure never needs to accept inbound connections from the platform. - **Windows and Linux**: Agents run as a Windows Service or Linux daemon. Any server or VM your deployments need to reach can run an agent. - **Self-updating**: Agents update themselves when new versions are released. Teams running Jaws Deploy Stack can point agents at a custom update server for controlled rollouts. - **Approved before execution**: When an agent connects for the first time it sends a handshake. An administrator approves it in the Jaws Deploy interface before the machine can receive any deployment work. > **Getting started - Installation takes minutes.** > > The Jaws Deploy interface provides a ready-to-run install script for each target. Run it on the target machine/deployment target, the Agent registers itself and sends a handshake, and an administrator approves the connection. From that point the machine is available as a deployment target. ### Learn more - [Jaws Deploy Cloud](https://www.jawsdeploy.net/platform/cloud) - [Jaws Deploy Stack](https://www.jawsdeploy.net/platform/stack) ## Jaws Deploy Stack Source: https://www.jawsdeploy.net/platform/stack Own the deployment control plane. Jaws Deploy Stack runs on-prem or in your private cloud with the same release model as [Jaws Deploy Cloud](https://www.jawsdeploy.net/platform/cloud) — no shared environment, no vendor hosting dependency. ### Stack vs. Cloud: the same product, different hosting Jaws Deploy Cloud and Jaws Deploy Stack use identical deployment logic. Projects, releases, environments, variables, targets, agents, and deployment history work the same way in both. The difference is who hosts and manages the application server. With Cloud, Jaws Deploy manages the platform on your behalf. With Stack, your team installs and runs it — on a server you control, inside a network boundary you define, with authentication integrated into your existing identity provider. ### Why teams choose Stack Stack is the right choice when deployment metadata, release history, credentials, or platform access must stay inside a private environment. Common drivers include compliance obligations that restrict external SaaS usage, on-prem network architectures where deployment targets are not reachable from the internet, and internal policies that require a single-tenant deployment platform with no shared infrastructure. - **Private by default**: Stack runs entirely on your infrastructure. Release records, deployment logs, credentials, and configuration never leave your environment. - **Compliance-ready hosting**: No shared infrastructure means no cross-tenant exposure. Stack fits environments where deployment tooling must satisfy internal security review, audit requirements, or sector-specific compliance controls. - **OIDC authentication**: Connect Stack to your existing identity provider through OIDC. No separate credential store for the deployment platform — your engineers log in with the same accounts they use everywhere else. - **Works inside private networks**: Stack can operate without internet exposure. Agents reach the Stack server over your internal network, which suits air-gapped infrastructure and strict egress policies. > **What to plan for - Stack requires a server you own and maintain.** > > Unlike Cloud, Stack puts platform operations in your team's hands. You choose when to update, how to back up, and how to expose the application to agents and users. For most teams this is a dedicated Linux server or VM — not a high-overhead operation, but a real hosting commitment. > > Jaws Deploy provides installation packages and guidance. Your team handles the operating system, storage, network access, and update schedule. ### Minimum infrastructure requirements Stack is designed to run efficiently on modest hardware. A single server or VM with 2–4 CPU cores, 4–8 GB RAM, and SSD-backed storage is sufficient for most team sizes. Linux on Debian or Ubuntu LTS is the recommended base — it gets the most testing and has the most straightforward installation path. For larger organizations, Stack can be deployed behind an internal load balancer or reverse proxy. Database and log storage can be pointed at external volumes for easier backup and growth management. ### Learn more - [Jaws Deploy Cloud](https://www.jawsdeploy.net/platform/cloud) - [Jaws Deploy Agents](https://www.jawsdeploy.net/platform/agents) ## Jaws Deploy Cloud Source: https://www.jawsdeploy.net/platform/cloud Run release automation in a managed Jaws Deploy environment. No installation, no control-plane maintenance, and the same deployment model you can later use with [Jaws Deploy Stack](https://www.jawsdeploy.net/platform/stack). ### Get started in minutes Jaws Deploy Cloud is the fastest way to put Jaws Deploy in front of a real team. Create an account, connect build output from your CI system, install [Agents](https://www.jawsdeploy.net/platform/agents) where deployment work needs to run, and start moving releases through environments. Cloud is built for teams that want deployment automation without owning the platform infrastructure. Jaws Deploy hosts and updates the application, while your team focuses on projects, releases, environments, variables, targets, and deployment history. ### The role Cloud plays Most teams already have CI. The missing piece is usually what happens after the build passes: which artifact went to which environment, what configuration was used, which machines were touched, and what happened during the deployment. Jaws Deploy Cloud gives that release workflow a dedicated place without asking the team to host the control plane. - **At a glance**: Managed control plane with projects, releases, environments, variables, feeds, and logs. Agents handle target execution. - **How Cloud fits**: Keep TeamCity, GitHub Actions, or any CI focused on build and test. Cloud handles release creation, promotion, scoped variables, target execution, and logs. - **Cloud owns the release record**: Agents execute close to your infrastructure. This keeps the hosted app simple while deployment work runs where your targets, credentials, and network access live. - **How it reaches your infra**: Cloud does not need direct access to every server. Agents run where deployments happen, execute work, and stream status back to the hosted app. ### Learn more - [Jaws Deploy Stack](https://www.jawsdeploy.net/platform/stack) - [Jaws Deploy Agents](https://www.jawsdeploy.net/platform/agents) # Solutions ## Small DevOps Teams Source: https://www.jawsdeploy.net/solutions/small-devops-teams If your release process is two people, a hand-drawn diagram, and a shell script nobody admits to maintaining - Jaws Deploy is built for that team. Real environments, real release history, zero platform team required. ### The team we built this for You are probably the person reading this *because* you are the deployment person. There is no separate platform team. There is no internal developer platform group. There is just a small group of engineers who all secretly believe they are the one keeping production alive on Friday afternoons. That team does not need a 90-page architecture diagram. It needs **a deployment tool that pays for itself in the first sprint and gets out of the way**. > **// The honest truth about small-team CD - You don't have a deployment problem. You have a 'who owns the deployment script' problem.** > > Small teams almost always start with a CI pipeline that grew an `if branch == main: deploy` block, then a script, then five scripts, then a Confluence page that explains the scripts. The tool is rarely the bottleneck - the *ownership* is. Jaws Deploy gives the deployment a home that is not 'whoever pushed last week'. ### What the first week actually looks like This matters more than the feature checklist. Most platforms describe what's possible at month six. Here is what a real small team gets done in the first five working days. ### The first five days - **Day 1 - signup to first deployment**: Create a workspace, install one agent on a staging box, create a project, deploy a hello-world package. Realistically: 45 minutes. - **Day 2 - model your environments**: Add Dev, Staging, Production. Tag your existing servers. Move the connection string out of your script into a scoped variable. Cry a little. Continue. - **Day 3 - first real release**: Hook your existing CI to call the Jaws Deploy API after a successful build. Create a release. Deploy it to Staging. Watch the live log instead of refreshing a CI tab. - **Day 4-5 - promotion and rollback**: Promote that exact release to Production. Realize you can deploy the previous release with one click. Sleep better on Friday. ### What you stop owning The pitch is not "new features". It is "things that used to be your problem are now somebody else's problem." That is the small-team value proposition. ### The maintenance tax disappears - **The custom deployment script** - Jaws Deploy steps replace 80% of it. The remaining 20% lives in a versioned step template, not a `deploy.ps1` nobody reviews. - **The artifact server** - the built-in package feed is just there. No second service to monitor, patch, or apologize for. - **The 'which version is in staging?' question** - the deployment history answers it in two clicks. - **The rollback procedure** - re-deploy the previous release. There is no procedure. That *is* the procedure. - **The audit trail** - every deployment leaves a record. When somebody asks 'who deployed what when', you have an answer that does not start with 'let me grep CI logs'. > **// Pricing reality check - We did the math so you don't have to argue with finance.** > > Small teams get murdered by per-target pricing. Jaws Deploy's plans are flat at the small end: a free tier that is actually usable, then a single Professional plan that includes a useful machine count. You will not get to month two and discover the bill scales with the number of pods. ### What stays in your control The risk with any deployment platform is the lock-in conversation six months in. We have opinions about this, and they show up in the product. **The deployment definition** Steps, variables, secrets, environments - all editable, exportable, scriptable. Want to spin up a clone for testing? The REST API is right there. **The control plane** We host it, we update it, we keep it alive. If you ever need it on your own infrastructure, [Jaws Deploy Stack](https://www.jawsdeploy.net/platform/stack) is the same product, self-hosted. No migration. ### The shape that scales The single best thing about starting with Jaws Deploy as a three-person team is what happens at thirty people. The model does not break. The environments you defined on Day 2 still work. The release process you set up still works. New team members read the deployment history and learn how things ship, instead of asking the one person who remembers. That is the bet - that *the workflow you build now should not be the one you replace at scale*. > **// What this is not - It is not an internal developer platform. It is not a Kubernetes management plane.** > > Some products in this space try to be your entire infrastructure layer. Jaws Deploy is a deployment automation tool. It does deployments very well, talks nicely to everything else, and lets you build the rest of your stack the way you want. The narrowness is the point. ### A short and honest list of who should *not* buy this To respect your time: ### Be honest about fit before the trial - Your team has zero servers and ships everything as a single GitHub Actions workflow to a single PaaS. CI is enough. Save your money. - You need a full GitOps controller that reconciles cluster state from a repo. Different shape of problem. - Your deployment requirements include orchestrating thousands of microservices across multiple clouds with traffic-shifting. We are good. We are not *that* product. - You enjoy maintaining your bash deployment script. That is a real preference and we respect it. ### How to evaluate this in an hour Spin up a free workspace. Connect it to a project you already ship. Do **one** real deployment to a non-production environment. That is the entire evaluation. If it took longer than an hour, tell us where it broke - we genuinely want to know. The small-team experience is what we measure most carefully. ## Agencies & Software Houses Source: https://www.jawsdeploy.net/solutions/agencies Agencies live or die by repeatability. A deployment that works for one client should work for the next twelve - without copy-pasting last quarter's pipeline. Jaws Deploy gives you templates that travel, environments that stay isolated, and an audit trail your account managers can actually read. ### The agency deployment shape An agency or software house has a problem most product teams don't: **the deployment is part of the deliverable**. The first time you ship for a client, you build a deployment. The next time, you do it again. The tenth time, you copy a pipeline from a previous project and slowly diverge it. By the twentieth client, nobody can answer 'which version of the deployment script does this client use'. Jaws Deploy treats that as the central problem. Templates are first-class. Per-client isolation is the default. The deployment knowledge stays with the agency, not buried inside one client's repo. > **// The hidden agency tax - Every new client used to cost you a week of deployment setup.** > > Twenty clients per year, one engineer-week each, billed at agency rates. That is the line item nobody writes down. The agencies that grow without imploding are the ones that turned 'set up the deployment' into a fifteen-minute task. That is what reusable step templates and shared variables actually buy you. ### How isolation works without copy-paste A workspace per client (or per business unit, or per practice area) gives you a clean separation: variables, secrets, environments, agents, deployment history. Then **step templates and script modules live above the workspace level** - so the deployment patterns that work for Client A automatically apply to Client B without sharing any sensitive data. ### Three layers that keep agencies sane - **Workspace per client**: Each client gets a clean tenancy. Variables, secrets, agents, deployments - none of it leaks between clients, ever. Account team-by-account team access if you want it. - **Shared templates**: Your library of reusable step templates - 'Deploy IIS site', 'Run migration', 'Swap blue-green' - is authored once and used in every workspace where it fits. - **Project per service**: Inside a client workspace, one project per deployable thing. Same pattern across every client. New engineers onboard once and recognize the layout everywhere. ### The two patterns agencies actually use In the field, agency setups tend to converge on one of two shapes. Both are fine. Picking deliberately matters more than picking the 'right' one. **One workspace per client** Clean isolation. Each client gets a dedicated workspace. Templates and modules are duplicated into each one or pulled from a shared library. Best when clients have strict data isolation requirements or when each client has very different infrastructure. **One workspace, projects per client** Less isolation, less overhead. All client work happens in a single workspace, with each client as a project (or group of projects). Best when client deployments are similar and your team prefers one pane of glass. > **// Sharp edge for agencies - Variables and secrets do not travel between workspaces. On purpose.** > > If you choose 'workspace per client', do not try to share a database password across workspaces. The platform won't let you - it is designed to keep one client's secret out of another client's deployment context. Plan your structure with that constraint in mind from day one. ### What the deployment process looks like for the tenth client The shape we hear back from agencies that have run this pattern for a year or two: the first client takes a week. The second client takes a day. The tenth client takes an afternoon. The improvement curve is real, and it comes from one specific thing - **the templates compound**. ### From signed contract to first deployment This is a real, abbreviated checklist from an agency running ~40 active client workspaces. ``` # 1. Create workspace for the new client (5 min) New-JawsDeployWorkspace -Name "AcmeCorp" # 2. Apply the agency's standard project template (10 min) Import-JawsDeployProjectTemplate ` -Workspace AcmeCorp ` -Template "WebApp-IIS-Standard" # 3. Register the client's machines, tag them (15 min) Register-JawsDeployMachine -Workspace AcmeCorp ` -Name "acme-staging-01" -Tags @("role:app","env:staging") # 4. Fill the client-specific variables (20 min) Set-JawsDeployVariable -Workspace AcmeCorp ` -Name "Db.ConnectionString" -Value $clientConnection # 5. Trigger first release from existing CI (10 min) # Total: ~one hour for a setup that used to be a week. ``` ### Audit trails the account team can read Agencies have a reporting problem that product teams don't. The client wants to know: was the patch deployed before the SLA window expired? Did the release that broke the cart get rolled back within an hour? Who actually pressed the button? Deployment history in Jaws Deploy answers those questions in the UI. Filter by client workspace, by environment, by date range. Hand the URL to the account manager. Move on. ### What the client-facing side of an agency actually asks for - **Per-client deployment exports** - CSV or REST API, filterable, ready for a status report. - **Time-to-recover metrics** - rollback frequency and duration, per client. - **Release attribution** - which engineer triggered which deployment, on which release, on which day. - **SLA-friendly timestamps** - everything is in UTC, immutable, and exists six months later. - **Plain-English release notes** - whatever your CI puts into the release at creation time is preserved with the release forever. > **// What this means for renewals - Reliability is a sales pitch. Audit trails are evidence.** > > When a client renews, the question they ask their procurement team is: 'did this agency cause us deployment problems?' If the answer comes from Jaws Deploy's history, it comes with timestamps, durations, and a clear 'yes/no'. If the answer comes from chat history and Word docs, it tends to go badly. The platform's existence is a renewal asset. ### The longer-term play Agencies that grow into product companies (a common arc) tend to keep their deployment platform. The patterns you build for client work become the deployment patterns for your own products. The templates already exist. The team already knows the model. There is no migration moment. That is why we lean on the agency case so hard - it is the test case for whether the abstractions scale across very different infrastructures, very different team sizes, and very different requirements. If we get this right, the rest is easier. ## Regulated / Self-Hosted Teams Source: https://www.jawsdeploy.net/solutions/self-hosted-deployments Some teams cannot or will not put deployment metadata in a SaaS. Jaws Deploy Stack is the same product as Cloud - same workflow, same UI, same agents - running entirely on your hardware, behind your firewall, on your update schedule. ### Why a self-hosted deployment platform exists Most teams should not self-host their deployment tool. It is one more thing to operate. But for the teams who *must* - because of regulation, network architecture, or hard-won security policy - the conversation is not 'should we?'. It is 'how do we do this without burning a quarter on the rollout?'. Jaws Deploy Stack exists for exactly that team. It is **the same product as Jaws Deploy Cloud**, packaged to run on your hardware. The product model, the UI, the agent protocol, the REST API - all identical. The difference is hosting. > **// The first thing to clarify - Self-hosting is a deployment decision, not a different product.** > > Stack is not a stripped-down or 'lite' version. It is the full platform, with the only meaningful caveat being that you (not us) are responsible for keeping the control plane alive. Most teams that adopt Stack first try Cloud, validate the workflow, then deploy Stack with a clear picture of what they are putting into their environment. ### Who actually needs this Being honest about fit is more useful than being optimistic. The teams that buy Stack tend to share three or four properties. ### The shape of a self-hosted buyer - **Regulated industry**: Finance, healthcare, defense, public sector, critical infrastructure. The reason isn't always written down - sometimes it's an interpretation of an audit standard that you can't argue with. - **Air-gap or restricted network**: Production cannot reach the public internet. Deployment automation must run inside the same network. There is no SaaS path here, regardless of policy. - **Data-residency requirements**: Deployment metadata (variable names, project structure, release notes) is itself considered sensitive. It cannot leave a specific jurisdiction or tenant. - **Custom update control**: You need to schedule platform updates with the rest of your change-management process. You cannot accept a vendor pushing updates on their schedule. ### What the self-hosted deployment looks like Stack is delivered as a set of containers and binaries you install on your own infrastructure. The control plane runs on whatever VM or Kubernetes setup your team standardizes on. Agents are exactly the same as on Cloud - they call outbound to the control plane, which now happens to be one of your servers instead of ours. ### What 'install Stack' actually means - **Control-plane host** - one VM or a small cluster, depending on team size. The control plane handles the API, UI, scheduling, and persistence. - **Database** - your existing MySQL or PostgreSQL instance. We don't ship a database; we connect to yours. - **Object storage** (optional) - for package feeds and large artifacts. S3-compatible, Azure Blob, or local disk if you must. - **Identity** - OIDC connector to your existing IdP. We do not store passwords; we lean on your auth. - **Agents** - installed on each deployment target, exactly like Cloud. > **// The migration question, asked early - Cloud-to-Stack is a documented migration. Stack-to-Cloud also works.** > > If you start on Cloud and need to move to Stack later (or vice versa), the data model is identical and a migration path is supported. We don't pretend this is a fifteen-minute job - it is not - but it is also not a 'rebuild the deployment platform' project. Teams that have done both directions report the move as 'a planned weekend'. ### What you take on by self-hosting It is fair to be direct about the operational cost. We are not going to tell you it is free. **Operating the control plane** Patching the host, monitoring uptime, sizing the database, rotating certificates, scaling for parallel deploys. None of this is exotic - it's the same stuff you do for any internal service. **The product itself** Bug fixes, new features, security patches, the agent protocol, the migration scripts between versions. Stack ships with the same release cadence as Cloud. ### Security posture, in concrete terms The security conversation tends to be the most important one in a self-hosted procurement. Here is what we put on the table without prompting. ### Things we make easy to verify or audit - **No outbound calls** from the control plane. Stack does not phone home. Telemetry is opt-in and clearly named. - **OIDC-first auth** - we do not store credentials; we delegate to your existing IdP and respect its session management. - **Encrypted at rest** - secrets in the database use envelope encryption with a key you control. - **Encrypted in transit** - mutual TLS between control plane and agents, configurable certificate authorities. - **Audit log** - every state-changing action writes to a structured audit table you can ship to your SIEM. - **Air-gap support** - including package feeds, agent installation, and platform updates via downloadable bundles. ### How OIDC integration looks in a Stack config Stack does not invent a user database. It uses yours. Most teams plug it into Okta, Entra ID, Keycloak, or an internal IdP in an afternoon. ``` # stack.config.toml [auth.oidc] issuer = "https://idp.internal.acme.example/realms/eng" client_id = "jawsdeploy-stack" client_secret = "${OIDC_CLIENT_SECRET}" scopes = ["openid", "profile", "email", "groups"] [auth.oidc.role_mapping] "group:platform-admin" = "Admin" "group:platform-team" = "WorkspaceAdmin" "group:engineering" = "Deployer" "group:everyone" = "Viewer" ``` > **// Compliance, plainly - We can support audits. We do not 'have' your certification.** > > Stack gives your team the building blocks to meet SOC 2, ISO 27001, HIPAA, FedRAMP, or sector-specific requirements. The certifications themselves are *yours* - they describe your operation, not ours. We provide architecture documentation, audit log exports, threat models, and direct technical contact during your auditor review. We do not pretend that running our software automatically makes you compliant. It doesn't. ### What to do next For most teams, the path is: try Cloud first, validate the workflow with a non-production project, then talk to us about Stack pricing and a pilot. Stack pricing is custom to the deployment footprint - we don't run a self-service signup for the self-hosted product because the conversation is *always* longer than that. The Cloud trial costs nothing. The Stack conversation usually starts with a thirty-minute call to understand what you are protecting and why. We won't try to talk you out of self-hosting - we will try to talk you into doing it for the right reasons. ## .NET & Windows Deployments Source: https://www.jawsdeploy.net/solutions/dotnet-windows-deployments Most CI/CD tools were built assuming Linux, containers, and a flat Kubernetes deployment. If your stack is IIS, Windows services, PowerShell, and a fleet of Server 2019 boxes - this page is for you. Jaws Deploy is built around exactly that stack. ### The .NET deployment elephant in the room If you ship .NET on Windows, you have probably noticed something: a lot of the popular deployment tooling treats your stack as a second-class citizen. The tutorials assume Docker. The plugins assume Linux. The 'easy path' is a container, and your application is a Windows service that absolutely will not be a container any time soon. Jaws Deploy was built by people who deploy Windows services for a living. PowerShell is the first language. IIS is a first-class concept. Windows services are a step type. The agent runs natively on Windows. None of this is an afterthought. > **// The unstated reason this matters - Your stack is not legacy. It is the stack that runs production.** > > There is an unhelpful narrative in the deployment-tooling world that says 'real teams ship containers'. Real teams ship whatever their customers paid for. A huge amount of the enterprise software running today is .NET on Windows Server, and it is not going anywhere this decade. A deployment tool that pretends otherwise is the wrong tool for the job - regardless of how shiny its homepage is. ### What 'first-class Windows' actually means We use that phrase a lot. Here is what it costs out to. ### Windows-native deployment building blocks - **IIS sites and app pools**: Built-in step templates for creating sites, swapping bindings, recycling app pools, applying `web.config` transforms - the things Octopus made common and Linux-first tools never bothered to copy. - **Windows services**: Install, configure, start, stop, restart, change recovery action, change RunAs user. Real `sc.exe` mechanics, with proper error handling, exposed as a step type. - **PowerShell-first**: Steps are PowerShell. Script modules are PowerShell. The SDK is PowerShell. If your team already writes PowerShell, you already write Jaws Deploy. - **Windows agent**: The agent is a native Windows service. It runs as a configurable service account, handles credentials properly, and does not require WSL or Docker Desktop to function. ### The IIS deployment, done correctly Deploying an IIS site is one of those things that *looks* simple and gets very fiddly very quickly. Stop the app pool, copy files, swap bindings, recycle, smoke-test. Every one of those steps has a way to go wrong, and the failure modes are usually invisible until users start calling support. Jaws Deploy ships an `IIS Site Deploy` template that handles the choreography correctly out of the box. The interesting part is what you *don't* have to write. ### Things teams used to learn the hard way - **Drains the app pool before recycle** - waits for active connections to complete with a configurable timeout. No mid-request kills. - **Applies `web.config` transforms in the right order** - XDT transforms run against the deployed package, not the source. - **Swaps bindings atomically** - the old binding stays live until the new site responds to a health check. - **Sets app pool identity correctly** - if you need to run as a specific service account, the credential handling does not leak into the log. - **Cleans up old versions** - keeps a configurable number of previous deployments on disk for emergency rollback. Older ones are pruned. ### From a real project, abbreviated This step deploys a Windows service that hosts a background processor. Notice what isn't here: any custom plumbing around stopping the service, copying files, or restarting it cleanly. ``` # Step template: Deploy Windows Service # # Inputs (filled per-project, per-environment): # ServiceName = "Acme.Checkout.Worker" # PackageName = "Acme.Checkout.Worker" # StopTimeoutSec = 60 # RunAsAccount = #{Service.Account} (scoped variable) # RunAsPassword = #{Service.Password} (secret) # StartAfterDeploy = true # # What the template does: # 1. Stop-Service -Name $ServiceName -Force -Timeout $StopTimeoutSec # 2. Wait for service to actually stop (not just 'pending stop') # 3. Extract package to "C:\Services\$ServiceName" # 4. Apply config transforms scoped to current environment # 5. sc.exe config $ServiceName obj= $RunAsAccount password= $RunAsPassword # 6. Start-Service -Name $ServiceName (if requested) # 7. Wait for service to actually be 'Running' (with timeout) # 8. Fail the deployment with a meaningful log line if any step fails ``` > **// The Octopus refugee question - If you came from Octopus, you will find this familiar - on purpose.** > > Jaws Deploy's product model owes a lot to the conventions Octopus established for Windows deployment. Projects, environments, variables, deployment processes, releases - the nouns are the same. We did this on purpose because the Windows world spent a decade learning that model and it actually works. We just rebuilt the product around it without the enterprise bloat. See the [Octopus Deploy Alternative](https://www.jawsdeploy.net/solutions/octopus-deploy-alternative) page if that is your specific path. ### PowerShell as a real language, not a punchline We write a lot of PowerShell. The PowerShell SDK is the same kind of thing - a real cmdlet library, properly typed, with pipeline support and the usual `-Verbose`, `-WhatIf`, `-Confirm` conventions. It is not a thin wrapper around `Invoke-RestMethod` with weird parameter names. **Real cmdlets** `New-JawsDeployRelease`, `Push-JawsDeployPackage`, `Invoke-JawsDeployDeployment`, `Get-JawsDeployVariableSet`. They pipe. They take credentials properly. They behave like cmdlets. **Idiomatic** TeamCity, Azure DevOps, Jenkins, GitHub Actions - all of them can call PowerShell. The deployment trigger becomes three lines of PowerShell after the build, not a 200-line YAML stanza. ### .NET Framework, .NET 6+, and the awkward middle A lot of teams have a mix - some apps on .NET Framework 4.x, some on .NET 6 or 8, some that nobody wants to talk about. Jaws Deploy treats this as the default case. Steps don't care which runtime your app uses. The package format is the same. The deployment process is the same. The way you describe variables and environments is the same. ### Things teams in this position care about - **Side-by-side runtime versions** - install the right runtime as a step in your pipeline; don't make the deployment tool care. - **Self-contained .NET 8 publishes** - shipped as zips, extracted onto the target, no global install dependency. - **`web.config` for Framework, `appsettings.json` for Core** - both are handled by the variable substitution layer. - **Service identity differs across apps** - scoped variables (per environment or per machine tag) handle the matrix without forking pipelines. > **// One small but persistent annoyance we fixed - Long file paths actually work.** > > Windows deployment tools have a long history of breaking on paths over 260 characters - usually inside a deeply nested `node_modules` or a TypeScript build output. Jaws Deploy's package extraction explicitly handles long paths on supported Windows versions. It sounds boring. It is boring. It used to ruin entire deployments at 11pm on a Friday. ### The honest scope of this offering If you are 100% containerized, on Linux, and ship through Helm charts - Jaws Deploy works, but other tools may suit you better. The reason this page exists, and the reason we built the product the way we did, is that the **Windows + .NET** deployment problem has been underserved for a decade. We are good at the underserved part. The rest of the world has plenty of options. ### How to test this in an hour Install a Jaws Deploy agent on a Windows VM you can afford to break. Point a project at a sample .NET app. Run the IIS site deploy template. Look at the log. If it doesn't feel like the deployment tool was actually built for your stack - tell us. We will be very surprised, and we will want to know what broke. ## Octopus Deploy Alternative Source: https://www.jawsdeploy.net/solutions/octopus-deploy-alternative Octopus got a lot right - the project model, the environment scoping, the release-then-promote shape. They also got more expensive, more complicated, and slower to evolve than many teams can stomach. Jaws Deploy is the alternative built by people who agreed with the model and disagreed with everything else. ### Let's be direct about why you're here If you are reading this page, one of three things is happening. Your renewal quote arrived and it was higher than the last one. Your team is spending hours per week on platform-side maintenance you did not sign up for. Or your CIO asked 'what else is out there?' and you are doing due diligence. All three are legitimate. We will not waste your time pretending otherwise. This page is the case for switching, written by people who like the Octopus model and built a smaller version of it on purpose. > **// The 'why now' moment - Most teams switch when one of three lines in the budget tips over.** > > There is no shame in being on Octopus. There are real reasons many teams are now looking around: the per-target pricing math stops working past a certain footprint, the platform itself takes increasing care and feeding, and the product roadmap has been - charitably - cautious. If none of those have hit your team, you may not need to switch yet. ### What we kept from the model We want to be specific about what carries over directly. If you came from Octopus, you should recognize most of this immediately - the labels are the same on purpose. ### The mental model, intact - **Projects**: Same concept. One project per deployable thing. The deployment process lives on the project, scoped to environments. No surprises here. - **Environments**: Logical stages of the release lifecycle, just like you're used to. Variables scope to them. Lifecycles enforce the order they're touched in. - **Deployment processes**: An ordered list of steps with built-in templates, custom templates, and PowerShell. If you can describe an Octopus process, you can describe a Jaws Deploy one. - **Releases & promotion**: Immutable release snapshots. Promote through environments. Same shape, same audit benefits, same 'deploy the previous release' rollback story. ### What we left behind This is the more interesting list. The things we deliberately did *not* port over - because they were where most of the pain came from. ### Things we decided not to do - **Per-target pricing as the primary axis.** It punishes the teams that succeed. Our pricing is flat-with-a-machine-allowance, then linear, then enterprise. No magic step functions. - **Tenant-per-customer as a major product concept.** Multi-tenant is real, but it became its own product surface in Octopus and accidentally complicated the simple case. We handle it through workspaces and tags - simpler, almost as flexible. - **The runbooks feature as a competing first-class concept.** Runbooks at Octopus's scale started to overlap with deployment processes confusingly. We let the deployment process be the deployment process; if you need ad-hoc ops automation, it's a script. - **Manual intervention as a step type with its own UI.** It exists, but it's deliberately understated. Most teams who used it discovered they needed an approval workflow elsewhere anyway. - **The kitchen sink of step templates we don't actually maintain.** We ship the well-loved ones, well-supported. Community step templates are an integration, not a Marketplace promise. > **// Where we differ on philosophy - Fewer features, owned more carefully.** > > The biggest single difference is product surface. Octopus accumulated capability over a long time. We have a smaller surface, on purpose. Some teams will hit a feature we don't have - and the honest answer is, yes, that is the trade-off. The deal is: less to learn, less to maintain, faster fixes, lower price. If you needed that one specific feature and we don't have it, we are probably not the right fit. If you needed the core 80% and were paying for the rest - that's the sweet spot. ### The pricing comparison, with our cards on the table We will not put a competitor's pricing on our marketing page - that ages badly and goes wrong in ways we don't want to manage. What we *will* tell you is the shape of our pricing and where the typical Octopus customer lands when they switch. **Per-target scaling** Pricing tied to the number of deployment targets and (historically) tenants. Predictable until it isn't - the bill jumps when you add a cluster of small machines, even if each one is doing very little work. **Flat plans, linear after a threshold** A free tier for proving the workflow. A flat Professional plan that covers most teams. Linear scaling after that, with no per-target tax. The bill correlates with team size, not infrastructure shape. ### Migration: the actual mechanics The migration question is where most evaluators get stuck, so let's be specific about what it looks like. Migrating from Octopus to Jaws Deploy is not zero work. It is also not a full project rewrite. ### From the teams who have done it - **Export the project structure** - Octopus exposes its model via REST API. Project names, environments, variable sets, and deployment process definitions are all readable. - **Recreate the projects in Jaws Deploy** - mostly mechanical. We have a migration helper for the common cases; the long tail is manual. - **Re-author custom step templates** - if you have custom step templates in Octopus, you'll rebuild them in Jaws Deploy. Same shape, different syntax for the body in some cases. - **Rewire the CI integration** - the call from your CI changes from 'create Octopus release' to 'create Jaws Deploy release'. Usually three lines. - **Cut over one project at a time** - we *strongly* recommend not big-banging this. Pick a non-critical project, migrate it end-to-end, run both platforms in parallel for a sprint. > **// Realistic timeline - Most teams complete the migration in 4-8 weeks of part-time work.** > > That number assumes a team with 10-30 projects, a few dozen environments, and the usual Octopus complexity. Less than 10 projects? Often two weeks. More than 100? It's a quarter. We have helped teams do all three. ### From Octopus to Jaws Deploy, side-by-side If you trigger Octopus releases from CI today, this is the part of your build script that changes. That's it. ``` # BEFORE - Octopus octo create-release ` --project "Checkout.Web" ` --version "2026.5.16.$buildNumber" ` --server "https://octopus.acme.example" ` --apiKey $env:OCTO_API_KEY ` --deployTo "Staging" # AFTER - Jaws Deploy New-JawsDeployRelease ` -Project "Checkout.Web" ` -Version "2026.5.16.$buildNumber" ` -Url "https://app.jawsdeploy.net" ` -ApiKey $env:JAWS_API_KEY ` -DeployTo "Staging" ``` ### The teams who don't switch Not every Octopus customer should move. To respect your time, here's who probably shouldn't. ### Honest reasons not to migrate - You are deeply invested in Octopus runbooks and ad-hoc automation, and your team uses them more than deployment processes. Different product fit. - You depend on a long tail of community step templates that we don't replicate. The 80% case is well-covered; the long tail is by design. - Your team has internal tooling built on top of the Octopus REST API that would need to be rewritten. Jaws Deploy has its own API - the data model is similar but the endpoints differ. - You're already on a healthy renewal cycle, the team is happy, the bill is fine. There's no virtue in switching for its own sake. ### How to actually evaluate this Spin up a free Jaws Deploy workspace. Pick *one* Octopus project and rebuild it - just the deployment process and the variables, not the entire history. Time how long it takes. If it took less than a day and the resulting workflow feels familiar - that's your migration test passed. If you want, [we will do that migration with you on a call](https://www.jawsdeploy.net/contact). One project, one hour, no commitment. That conversation is usually more useful than reading another comparison page. ## TeamCity Integration Source: https://www.jawsdeploy.net/solutions/teamcity-integration TeamCity is one of the best CI servers ever shipped. It is not a deployment automation platform, even when teams try very hard to make it act like one. Jaws Deploy is the deployment half of that pair - environment-aware, release-centric, and built to start where TeamCity finishes. ### The pattern that just works If you've been on TeamCity for a few years and your deployments live inside TeamCity build configurations, you've probably noticed the tension: build configurations are great at producing artifacts, and they are *fine* at deploying them, until they aren't. Then you have a build configuration with thirty steps, half of which are deployment-only, and the deployment knowledge is encoded in the order of those steps and the parameters somebody set six months ago. The healthier shape: TeamCity stays a build server. It compiles, tests, packages, and pushes the artifact. From there, a separate deployment platform - Jaws Deploy - handles the release. Two tools, each one doing its job well, each one staying out of the other's lane. > **// The single largest lift from this pattern - Deployment configuration stops living in TeamCity.** > > When the deployment lives inside a TeamCity build configuration, configuration changes go through the same approval (or non-approval) process as build changes. That tends to be 'whoever has TeamCity admin can change anything'. Splitting the deployment into Jaws Deploy means deployment changes have their own audit trail, their own permissions, and their own review cycle - separate from 'we adjusted the build runner.' ### Two ways to wire them together There are two clean integration patterns. The right one depends on whether you want TeamCity to trigger releases automatically, or whether your release process has its own cadence. **Auto-release after successful build** The last step in the TeamCity build configuration calls Jaws Deploy to create a release with the just-built artifact, optionally targeting Dev. The release is then promoted from Dev to Staging to Production through Jaws Deploy. This is the common case. **Manual release from a confirmed build** TeamCity builds and tests, but does *not* automatically create a release. Engineers create releases in Jaws Deploy when they're ready, pulling from the most recent successful TeamCity build. Used by teams with strict release governance. ### The TeamCity plugin For Pattern A, we ship a TeamCity build runner that handles the integration as a one-step build step. You configure it in the TeamCity UI: which Jaws Deploy server, which project, where the artifact lives. The plugin handles the rest - creating the release with the right version number, attaching the right packages, and optionally deploying to a starting environment. ### What the TeamCity plugin does for you - **Pushes artifacts**: From the TeamCity build output to the Jaws Deploy package feed. Handles versioning, retries on transient failures, and respects the workspace's auth model. - **Creates the release**: With the right version (from TeamCity's build number, build counter, or a custom pattern), the right release notes (pulled from VCS commits), and the right package versions. - **Optionally deploys**: Triggers a deployment to a starting environment (usually Dev or an integration env) so the build feedback loop closes immediately. - **Streams status back**: TeamCity build log shows the Jaws Deploy deployment status inline, so the build report tells you whether the release succeeded - not just that it was triggered. ### One build step, conceptually The plugin's build step in TeamCity has a handful of fields. Most teams set up a template once and reuse it across projects. ``` # TeamCity Build Step: Jaws Deploy - Create Release Jaws Deploy Server : https://app.jawsdeploy.net Workspace : default Project : %env.PROJECT_NAME% Version : %build.number% Package : %env.PROJECT_NAME%/%build.number% Release Notes Source : VCS commits since last build Deploy To : Dev (optional - leave blank for manual) # After the build succeeds, this step runs. # After this step runs, your release exists in Jaws Deploy. # After (optional) deploy: Dev is live with the new build. ``` ### When the plugin isn't the right tool The plugin is the easy path, but it isn't the only path. Some teams have build pipelines that need more control over when, what, and how the release is created. For those cases, the same integration is available through the PowerShell SDK and the REST API - just called from a script step in TeamCity instead of the plugin. ### Plugin is good. Sometimes you need more. - **Conditional release creation** - 'only create a release if the build was on the `main` branch and the tag matches a pattern'. The plugin's conditions are limited; the SDK is full PowerShell. - **Multi-package releases** - if a single release pulls artifacts from multiple TeamCity build configurations, the SDK is much easier to choreograph. - **Custom release notes** - if you generate release notes from JIRA or a wiki, you'll script that in a build step before the SDK call anyway. - **Non-PowerShell environments** - if your TeamCity agents are Linux-only, use the REST API from bash. Same underlying calls, no language constraint. > **// A common confusion we'll clear up - 'TeamCity has deployment features. Why do I need another tool?'** > > TeamCity has *deployment-capable build steps*. That is a different thing from a deployment automation platform. Build steps can deploy - they can also brick a production database, and there's no first-class concept of 'release', 'environment', or 'promotion' inside TeamCity. You can fake it with parameters, project hierarchies, and template tricks. Eventually somebody fakes it incorrectly and Production becomes Staging for an hour. A tool built for deployment makes that mistake harder to make. ### The migration off 'TeamCity deploys everything' Most teams reading this are already on TeamCity and have grown an unhealthy amount of deployment logic inside it. The migration is gradual on purpose. ### Don't big-bang this - **Phase 1** - install the Jaws Deploy plugin in TeamCity. Pick one project. Have TeamCity create a release in Jaws Deploy after each successful build. Don't change anything else yet. - **Phase 2** - move that one project's *deployment* into Jaws Deploy. TeamCity's deployment steps are now disabled; Jaws Deploy handles Dev, Staging, Production for that project. - **Phase 3** - migrate the rest of the projects, one at a time. Each one looks the same as the first. - **Phase 4** - clean up. Delete the now-unused TeamCity build configurations or build steps. The build configurations stop being 200 lines long. **TeamCity does everything** Build → test → package → push artifact → deploy to dev → smoke test → tag → deploy to staging → smoke test → wait for approval → deploy to prod. All in TeamCity. All in build configurations. All tangled. **Two tools, clean handoff** TeamCity: build → test → package → push to Jaws Deploy → create release. Jaws Deploy: dev → staging → prod with proper environment scoping, variables, and a real audit trail. Each tool stays in its lane. ### The honest reason this combo works TeamCity and Jaws Deploy do not compete. We have no interest in being a CI server. JetBrains has no particular interest in being a release platform. The integration is the obvious answer, and the only reason teams haven't been doing it for years is that the deployment-side options were either too expensive or too heavy. We are explicitly the lighter-weight alternative built to pair with what you already have - not to replace it. If you're on TeamCity and your deployment process has outgrown build configurations - this is the path. # Pricing Source: https://www.jawsdeploy.net/pricing Start free, move to a flat Professional plan when the team is ready, and keep the dedicated tier for self-hosted or enterprise setups. ## Plans ### Starter: €0 forever For small teams and individuals. Prove the workflow with real deployments. - 1 workspace - 2 machines - 5 projects - Community support No card required. ### Professional: €45/mo + €2/machine For growing agile teams. The plan with the clearest path to scale. - Unlimited workspaces and users - Parallel deploys - Priority support - 100 GB storage Includes 10 machines. ### Dedicated: Talk to us For self-hosted or regulated teams. Private deployments, SSO, and support terms for larger orgs. - Cloud or on-prem - SSO + RBAC - Migration assistance Available cloud or self-hosted. ## Plan comparison | Included | Starter | Professional | Dedicated | |---|---|---|---| | **Capacity** | | | | | Machines | 2 | 10 (+€2 each) | Unlimited | | Cloud targets | 2 | 10 (+€2 each) | Unlimited | | Projects | 5 | 20 (+€2 each) | Unlimited | | Storage | 5 GB | 100 GB (+€0.30/GB) | 2 TB | | Parallel deployments | 1 | 5 (+€1 each) | 20 | | Workspaces | 1 | Unlimited | Unlimited | | User accounts | 1 | Unlimited | Unlimited | | **Features** | | | | | Real deployments | Yes | Yes | Yes | | Environment-aware variables | Yes | Yes | Yes | | Self-hosting | - | Yes | Yes | | Priority support | - | Yes | Yes | | SSO + RBAC | - | - | Yes | | Audit log | - | - | Yes |