Vercel - Deploy

Octopus.Script exported 2026-07-20 by Meanski belongs to ‘Vercel’ category.

Triggers a git-connected deployment of a Vercel project via the Vercel REST API, and optionally waits for the deployment to finish building. Supports projects connected to GitHub, GitLab, and Bitbucket repositories.

The Vercel project must be connected to the repository via Vercel’s git integration (this also covers private repositories); the step never accesses the repository directly, so no git credentials are needed in Octopus.

Sets the output variables DeploymentId, DeploymentUrl, and DeploymentState for use in later steps.

Requires curl and either jq or python3 on the worker.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

API Token

VercelDeploy.ApiToken =

A Vercel API token. Create one at vercel.com/account/tokens. Consider binding this to a sensitive project variable (e.g. #{Vercel.ApiToken}) so it can be scoped per environment and rotated in one place.

Project Name

VercelDeploy.ProjectName =

The name of the Vercel project to deploy.

Git Provider

VercelDeploy.GitProvider = github

The git provider the Vercel project is connected to.

Git Ref

VercelDeploy.GitRef = main

The branch name or commit SHA to deploy. For repeatable deployments across environments, bind this to a commit SHA captured at release creation (e.g. from build information or a prompted variable) rather than a branch name, so every environment deploys the same commit.

Repository Owner

VercelDeploy.RepoOrg =

GitHub org/username or Bitbucket workspace. Not required for GitLab.

Repository Name

VercelDeploy.RepoName =

The repository name or Bitbucket slug. Not required for GitLab.

Repo ID

VercelDeploy.RepoId =

GitLab project ID. Only required for GitLab.

Deployment Target

VercelDeploy.Target = production

Valid values: production, staging, or a custom environment identifier.

Team ID

VercelDeploy.TeamId =

Vercel team ID. Only required for team-owned projects.

Wait For Deployment

VercelDeploy.WaitForDeployment = True

If enabled, the step polls the Vercel API until the deployment reaches the READY state, and fails if the build errors, is canceled, or the timeout is reached.

Wait Timeout (seconds)

VercelDeploy.TimeoutSeconds = 600

How long to wait for the deployment to become READY before failing the step. Only used when Wait For Deployment is enabled.

Script body

Steps based on this template will execute the following Bash script.

#!/bin/bash

# Vercel - Deploy
# Triggers a git-connected deployment on Vercel via the REST API, and
# optionally waits for the deployment to finish building.
# API reference: https://vercel.com/docs/rest-api/reference/endpoints/deployments/create-a-new-deployment

# Read step parameters
ApiToken=$(get_octopusvariable "VercelDeploy.ApiToken")
ProjectName=$(get_octopusvariable "VercelDeploy.ProjectName")
GitProvider=$(get_octopusvariable "VercelDeploy.GitProvider")
GitRef=$(get_octopusvariable "VercelDeploy.GitRef")
RepoOrg=$(get_octopusvariable "VercelDeploy.RepoOrg")
RepoName=$(get_octopusvariable "VercelDeploy.RepoName")
RepoId=$(get_octopusvariable "VercelDeploy.RepoId")
Target=$(get_octopusvariable "VercelDeploy.Target")
TeamId=$(get_octopusvariable "VercelDeploy.TeamId")
WaitForDeployment=$(get_octopusvariable "VercelDeploy.WaitForDeployment")
TimeoutSeconds=$(get_octopusvariable "VercelDeploy.TimeoutSeconds")

# Validate required tooling

if ! command -v curl >/dev/null 2>&1; then
  echo "ERROR: curl is required on the worker to run this step." >&2
  exit 1
fi

if command -v jq >/dev/null 2>&1; then
  JSON_TOOL="jq"
elif command -v python3 >/dev/null 2>&1; then
  JSON_TOOL="python3"
else
  echo "ERROR: jq or python3 is required on the worker to build and parse JSON." >&2
  exit 1
fi

# Prints the JSON-encoded (quoted) form of $1
json_escape() {
  if [[ "$JSON_TOOL" == "jq" ]]; then
    jq -cn --arg v "$1" '$v'
  else
    python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1"
  fi
}

# Reads JSON on stdin and prints the value at dotted path $1 (empty if missing or null)
json_get() {
  if [[ "$JSON_TOOL" == "jq" ]]; then
    jq -r ".$1 // empty" 2>/dev/null
  else
    python3 -c '
import json, sys
try:
    data = json.load(sys.stdin)
except Exception:
    sys.exit(0)
cur = data
for part in sys.argv[1].split("."):
    if isinstance(cur, dict) and part in cur:
        cur = cur[part]
    else:
        sys.exit(0)
if cur is None:
    sys.exit(0)
print(cur if not isinstance(cur, (dict, list)) else json.dumps(cur))
' "$1"
  fi
}

# Validate required parameters

if [[ -z "$ApiToken" ]]; then
  echo "ERROR: ApiToken is required." >&2
  exit 1
fi

if [[ -z "$ProjectName" ]]; then
  echo "ERROR: ProjectName is required." >&2
  exit 1
fi

if [[ -z "$GitProvider" ]]; then
  echo "ERROR: GitProvider is required (github, gitlab, bitbucket)." >&2
  exit 1
fi

if [[ -z "$GitRef" ]]; then
  echo "ERROR: GitRef is required (branch name or commit SHA)." >&2
  exit 1
fi

TimeoutSeconds="${TimeoutSeconds:-600}"
if ! [[ "$TimeoutSeconds" =~ ^[0-9]+$ ]]; then
  echo "ERROR: Timeout must be a whole number of seconds." >&2
  exit 1
fi

# Normalise provider to lowercase
GitProvider=$(echo "$GitProvider" | tr '[:upper:]' '[:lower:]')

# Build gitSource payload

REF_JSON=$(json_escape "$GitRef")

case "$GitProvider" in
  github)
    if [[ -z "$RepoOrg" || -z "$RepoName" ]]; then
      echo "ERROR: RepoOrg and RepoName are required for GitHub deployments." >&2
      exit 1
    fi
    GIT_SOURCE="{\"type\":\"github\",\"org\":$(json_escape "$RepoOrg"),\"repo\":$(json_escape "$RepoName"),\"ref\":$REF_JSON}"
    ;;
  gitlab)
    if [[ -z "$RepoId" ]]; then
      echo "ERROR: RepoId is required for GitLab deployments." >&2
      exit 1
    fi
    GIT_SOURCE="{\"type\":\"gitlab\",\"projectId\":$(json_escape "$RepoId"),\"ref\":$REF_JSON}"
    ;;
  bitbucket)
    if [[ -z "$RepoOrg" || -z "$RepoName" ]]; then
      echo "ERROR: RepoOrg (workspace) and RepoName (slug) are required for Bitbucket deployments." >&2
      exit 1
    fi
    GIT_SOURCE="{\"type\":\"bitbucket\",\"owner\":$(json_escape "$RepoOrg"),\"slug\":$(json_escape "$RepoName"),\"ref\":$REF_JSON}"
    ;;
  *)
    echo "ERROR: Unsupported GitProvider '$GitProvider'. Must be one of: github, gitlab, bitbucket." >&2
    exit 1
    ;;
esac

# Build request body

# Target defaults to "production". Valid values: production, staging, or a custom environment identifier.
DEPLOY_TARGET="${Target:-production}"

BODY="{\"name\":$(json_escape "$ProjectName"),\"gitSource\":$GIT_SOURCE,\"target\":$(json_escape "$DEPLOY_TARGET")}"

# Build URL with optional teamId

API_URL="https://api.vercel.com/v13/deployments"
if [[ -n "$TeamId" ]]; then
  API_URL="${API_URL}?teamId=${TeamId}"
fi

# Make the API request

echo "Triggering Vercel deployment..."
echo "  Project : $ProjectName"
echo "  Provider: $GitProvider"
echo "  Ref     : $GitRef"
echo "  Target  : $DEPLOY_TARGET"
[[ -n "$TeamId" ]] && echo "  Team ID : $TeamId"
echo ""

RAW=$(curl --silent --show-error --max-time 120 --write-out $'\n%{http_code}' \
  --request POST "$API_URL" \
  --header "Authorization: Bearer $ApiToken" \
  --header "Content-Type: application/json" \
  --data "$BODY")

CURL_EXIT=$?

if [[ $CURL_EXIT -ne 0 ]]; then
  echo "ERROR: curl request failed (exit code $CURL_EXIT)." >&2
  exit 1
fi

HTTP_STATUS=$(echo "$RAW" | tail -n 1)
RESPONSE=$(echo "$RAW" | sed '$d')

# Parse response

ERROR_CODE=$(echo "$RESPONSE" | json_get "error.code")
ERROR_MSG=$(echo "$RESPONSE" | json_get "error.message")

if [[ -n "$ERROR_CODE" || "$HTTP_STATUS" -ge 400 ]]; then
  echo "ERROR: Vercel API returned an error (HTTP $HTTP_STATUS)." >&2
  [[ -n "$ERROR_CODE" ]] && echo "  Code   : $ERROR_CODE" >&2
  [[ -n "$ERROR_MSG" ]] && echo "  Message: $ERROR_MSG" >&2
  exit 1
fi

# Extract deployment details

DEPLOY_ID=$(echo "$RESPONSE" | json_get "id")
DEPLOY_URL=$(echo "$RESPONSE" | json_get "url")
DEPLOY_STATE=$(echo "$RESPONSE" | json_get "readyState")

if [[ -z "$DEPLOY_ID" ]]; then
  echo "ERROR: Could not read the deployment id from the Vercel API response." >&2
  exit 1
fi

echo "Deployment created."
echo "  ID    : $DEPLOY_ID"
echo "  State : $DEPLOY_STATE"
echo "  URL   : https://$DEPLOY_URL"

set_octopusvariable "DeploymentId" "$DEPLOY_ID"
set_octopusvariable "DeploymentUrl" "https://$DEPLOY_URL"
set_octopusvariable "DeploymentState" "$DEPLOY_STATE"

if [[ "$WaitForDeployment" != "True" ]]; then
  echo ""
  echo "Wait for deployment is disabled; not waiting for the build to finish."
  exit 0
fi

# Poll until the deployment is READY, fails, or the timeout is reached

STATUS_URL="https://api.vercel.com/v13/deployments/${DEPLOY_ID}"
if [[ -n "$TeamId" ]]; then
  STATUS_URL="${STATUS_URL}?teamId=${TeamId}"
fi

echo ""
echo "Waiting up to ${TimeoutSeconds}s for the deployment to become READY..."

INTERVAL=10
ELAPSED=0

while true; do
  STATUS_RAW=$(curl --silent --show-error --max-time 60 \
    --header "Authorization: Bearer $ApiToken" \
    "$STATUS_URL")

  if [[ $? -ne 0 ]]; then
    echo "WARNING: Status check failed; retrying..." >&2
  else
    DEPLOY_STATE=$(echo "$STATUS_RAW" | json_get "readyState")
    echo "  [${ELAPSED}s] State: ${DEPLOY_STATE:-UNKNOWN}"

    case "$DEPLOY_STATE" in
      READY)
        set_octopusvariable "DeploymentState" "$DEPLOY_STATE"
        echo ""
        echo "Deployment is READY: https://$DEPLOY_URL"
        exit 0
        ;;
      ERROR|CANCELED)
        set_octopusvariable "DeploymentState" "$DEPLOY_STATE"
        echo "ERROR: Deployment finished in state $DEPLOY_STATE. Check the build logs in the Vercel dashboard." >&2
        exit 1
        ;;
    esac
  fi

  if (( ELAPSED >= TimeoutSeconds )); then
    set_octopusvariable "DeploymentState" "${DEPLOY_STATE:-TIMED_OUT}"
    echo "ERROR: Timed out after ${TimeoutSeconds}s waiting for the deployment to become READY (last state: ${DEPLOY_STATE:-unknown})." >&2
    exit 1
  fi

  sleep "$INTERVAL"
  ELAPSED=$((ELAPSED + INTERVAL))
done

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "1f76f2e4-7f3b-4eb1-8278-6503fcd78dcd",
  "Name": "Vercel - Deploy",
  "Description": "Triggers a git-connected deployment of a [Vercel](https://vercel.com) project via the [Vercel REST API](https://vercel.com/docs/rest-api), and optionally waits for the deployment to finish building. Supports projects connected to GitHub, GitLab, and Bitbucket repositories.\n\nThe Vercel project must be connected to the repository via Vercel's git integration (this also covers private repositories); the step never accesses the repository directly, so no git credentials are needed in Octopus.\n\nSets the output variables `DeploymentId`, `DeploymentUrl`, and `DeploymentState` for use in later steps.\n\nRequires `curl` and either `jq` or `python3` on the worker.",
  "Version": 1,
  "ExportedAt": "2026-07-20T10:22:34.000Z",
  "ActionType": "Octopus.Script",
  "Author": "Meanski",
  "Packages": [],
  "Parameters": [
    {
      "Id": "ecbfaa42-7ff0-4830-9fcb-e759e69b92ae",
      "Name": "VercelDeploy.ApiToken",
      "Label": "API Token",
      "HelpText": "A Vercel API token. Create one at [vercel.com/account/tokens](https://vercel.com/account/tokens). Consider binding this to a sensitive project variable (e.g. `#{Vercel.ApiToken}`) so it can be scoped per environment and rotated in one place.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "ee646811-9d6b-49b2-b9ba-eaa80152bf62",
      "Name": "VercelDeploy.ProjectName",
      "Label": "Project Name",
      "HelpText": "The name of the Vercel project to deploy.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "c28854cc-54fb-4358-b31a-1a1da09174cc",
      "Name": "VercelDeploy.GitProvider",
      "Label": "Git Provider",
      "HelpText": "The git provider the Vercel project is connected to.",
      "DefaultValue": "github",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "github|GitHub\ngitlab|GitLab\nbitbucket|Bitbucket"
      }
    },
    {
      "Id": "8df5b54c-d212-4a95-acae-ef4b7fca1601",
      "Name": "VercelDeploy.GitRef",
      "Label": "Git Ref",
      "HelpText": "The branch name or commit SHA to deploy. For repeatable deployments across environments, bind this to a commit SHA captured at release creation (e.g. from build information or a prompted variable) rather than a branch name, so every environment deploys the same commit.",
      "DefaultValue": "main",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "b62440cb-17c8-4009-aee4-cd1966189f91",
      "Name": "VercelDeploy.RepoOrg",
      "Label": "Repository Owner",
      "HelpText": "GitHub org/username or Bitbucket workspace. Not required for GitLab.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "d13fdb33-d825-4921-9b3e-b5f1d42c0e94",
      "Name": "VercelDeploy.RepoName",
      "Label": "Repository Name",
      "HelpText": "The repository name or Bitbucket slug. Not required for GitLab.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "96d2e419-efc5-42bd-8f58-e54045a3827c",
      "Name": "VercelDeploy.RepoId",
      "Label": "Repo ID",
      "HelpText": "GitLab project ID. Only required for GitLab.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "78b9befc-ad68-4b83-b243-cbf1bd9f7cad",
      "Name": "VercelDeploy.Target",
      "Label": "Deployment Target",
      "HelpText": "Valid values: production, staging, or a custom environment identifier.",
      "DefaultValue": "production",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "8fd29d5c-7f3a-406d-8ebb-aec52883ec2a",
      "Name": "VercelDeploy.TeamId",
      "Label": "Team ID",
      "HelpText": "Vercel team ID. Only required for team-owned projects.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "b555545c-33e8-4ea8-9c6f-bb78e2d3a7b1",
      "Name": "VercelDeploy.WaitForDeployment",
      "Label": "Wait For Deployment",
      "HelpText": "If enabled, the step polls the Vercel API until the deployment reaches the READY state, and fails if the build errors, is canceled, or the timeout is reached.",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "8a0ea110-68ff-4a91-86ea-0261bb677729",
      "Name": "VercelDeploy.TimeoutSeconds",
      "Label": "Wait Timeout (seconds)",
      "HelpText": "How long to wait for the deployment to become READY before failing the step. Only used when Wait For Deployment is enabled.",
      "DefaultValue": "600",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "Bash",
    "Octopus.Action.Script.ScriptBody": "#!/bin/bash\n\n# Vercel - Deploy\n# Triggers a git-connected deployment on Vercel via the REST API, and\n# optionally waits for the deployment to finish building.\n# API reference: https://vercel.com/docs/rest-api/reference/endpoints/deployments/create-a-new-deployment\n\n# Read step parameters\nApiToken=$(get_octopusvariable \"VercelDeploy.ApiToken\")\nProjectName=$(get_octopusvariable \"VercelDeploy.ProjectName\")\nGitProvider=$(get_octopusvariable \"VercelDeploy.GitProvider\")\nGitRef=$(get_octopusvariable \"VercelDeploy.GitRef\")\nRepoOrg=$(get_octopusvariable \"VercelDeploy.RepoOrg\")\nRepoName=$(get_octopusvariable \"VercelDeploy.RepoName\")\nRepoId=$(get_octopusvariable \"VercelDeploy.RepoId\")\nTarget=$(get_octopusvariable \"VercelDeploy.Target\")\nTeamId=$(get_octopusvariable \"VercelDeploy.TeamId\")\nWaitForDeployment=$(get_octopusvariable \"VercelDeploy.WaitForDeployment\")\nTimeoutSeconds=$(get_octopusvariable \"VercelDeploy.TimeoutSeconds\")\n\n# Validate required tooling\n\nif ! command -v curl >/dev/null 2>&1; then\n  echo \"ERROR: curl is required on the worker to run this step.\" >&2\n  exit 1\nfi\n\nif command -v jq >/dev/null 2>&1; then\n  JSON_TOOL=\"jq\"\nelif command -v python3 >/dev/null 2>&1; then\n  JSON_TOOL=\"python3\"\nelse\n  echo \"ERROR: jq or python3 is required on the worker to build and parse JSON.\" >&2\n  exit 1\nfi\n\n# Prints the JSON-encoded (quoted) form of $1\njson_escape() {\n  if [[ \"$JSON_TOOL\" == \"jq\" ]]; then\n    jq -cn --arg v \"$1\" '$v'\n  else\n    python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' \"$1\"\n  fi\n}\n\n# Reads JSON on stdin and prints the value at dotted path $1 (empty if missing or null)\njson_get() {\n  if [[ \"$JSON_TOOL\" == \"jq\" ]]; then\n    jq -r \".$1 // empty\" 2>/dev/null\n  else\n    python3 -c '\nimport json, sys\ntry:\n    data = json.load(sys.stdin)\nexcept Exception:\n    sys.exit(0)\ncur = data\nfor part in sys.argv[1].split(\".\"):\n    if isinstance(cur, dict) and part in cur:\n        cur = cur[part]\n    else:\n        sys.exit(0)\nif cur is None:\n    sys.exit(0)\nprint(cur if not isinstance(cur, (dict, list)) else json.dumps(cur))\n' \"$1\"\n  fi\n}\n\n# Validate required parameters\n\nif [[ -z \"$ApiToken\" ]]; then\n  echo \"ERROR: ApiToken is required.\" >&2\n  exit 1\nfi\n\nif [[ -z \"$ProjectName\" ]]; then\n  echo \"ERROR: ProjectName is required.\" >&2\n  exit 1\nfi\n\nif [[ -z \"$GitProvider\" ]]; then\n  echo \"ERROR: GitProvider is required (github, gitlab, bitbucket).\" >&2\n  exit 1\nfi\n\nif [[ -z \"$GitRef\" ]]; then\n  echo \"ERROR: GitRef is required (branch name or commit SHA).\" >&2\n  exit 1\nfi\n\nTimeoutSeconds=\"${TimeoutSeconds:-600}\"\nif ! [[ \"$TimeoutSeconds\" =~ ^[0-9]+$ ]]; then\n  echo \"ERROR: Timeout must be a whole number of seconds.\" >&2\n  exit 1\nfi\n\n# Normalise provider to lowercase\nGitProvider=$(echo \"$GitProvider\" | tr '[:upper:]' '[:lower:]')\n\n# Build gitSource payload\n\nREF_JSON=$(json_escape \"$GitRef\")\n\ncase \"$GitProvider\" in\n  github)\n    if [[ -z \"$RepoOrg\" || -z \"$RepoName\" ]]; then\n      echo \"ERROR: RepoOrg and RepoName are required for GitHub deployments.\" >&2\n      exit 1\n    fi\n    GIT_SOURCE=\"{\\\"type\\\":\\\"github\\\",\\\"org\\\":$(json_escape \"$RepoOrg\"),\\\"repo\\\":$(json_escape \"$RepoName\"),\\\"ref\\\":$REF_JSON}\"\n    ;;\n  gitlab)\n    if [[ -z \"$RepoId\" ]]; then\n      echo \"ERROR: RepoId is required for GitLab deployments.\" >&2\n      exit 1\n    fi\n    GIT_SOURCE=\"{\\\"type\\\":\\\"gitlab\\\",\\\"projectId\\\":$(json_escape \"$RepoId\"),\\\"ref\\\":$REF_JSON}\"\n    ;;\n  bitbucket)\n    if [[ -z \"$RepoOrg\" || -z \"$RepoName\" ]]; then\n      echo \"ERROR: RepoOrg (workspace) and RepoName (slug) are required for Bitbucket deployments.\" >&2\n      exit 1\n    fi\n    GIT_SOURCE=\"{\\\"type\\\":\\\"bitbucket\\\",\\\"owner\\\":$(json_escape \"$RepoOrg\"),\\\"slug\\\":$(json_escape \"$RepoName\"),\\\"ref\\\":$REF_JSON}\"\n    ;;\n  *)\n    echo \"ERROR: Unsupported GitProvider '$GitProvider'. Must be one of: github, gitlab, bitbucket.\" >&2\n    exit 1\n    ;;\nesac\n\n# Build request body\n\n# Target defaults to \"production\". Valid values: production, staging, or a custom environment identifier.\nDEPLOY_TARGET=\"${Target:-production}\"\n\nBODY=\"{\\\"name\\\":$(json_escape \"$ProjectName\"),\\\"gitSource\\\":$GIT_SOURCE,\\\"target\\\":$(json_escape \"$DEPLOY_TARGET\")}\"\n\n# Build URL with optional teamId\n\nAPI_URL=\"https://api.vercel.com/v13/deployments\"\nif [[ -n \"$TeamId\" ]]; then\n  API_URL=\"${API_URL}?teamId=${TeamId}\"\nfi\n\n# Make the API request\n\necho \"Triggering Vercel deployment...\"\necho \"  Project : $ProjectName\"\necho \"  Provider: $GitProvider\"\necho \"  Ref     : $GitRef\"\necho \"  Target  : $DEPLOY_TARGET\"\n[[ -n \"$TeamId\" ]] && echo \"  Team ID : $TeamId\"\necho \"\"\n\nRAW=$(curl --silent --show-error --max-time 120 --write-out $'\\n%{http_code}' \\\n  --request POST \"$API_URL\" \\\n  --header \"Authorization: Bearer $ApiToken\" \\\n  --header \"Content-Type: application/json\" \\\n  --data \"$BODY\")\n\nCURL_EXIT=$?\n\nif [[ $CURL_EXIT -ne 0 ]]; then\n  echo \"ERROR: curl request failed (exit code $CURL_EXIT).\" >&2\n  exit 1\nfi\n\nHTTP_STATUS=$(echo \"$RAW\" | tail -n 1)\nRESPONSE=$(echo \"$RAW\" | sed '$d')\n\n# Parse response\n\nERROR_CODE=$(echo \"$RESPONSE\" | json_get \"error.code\")\nERROR_MSG=$(echo \"$RESPONSE\" | json_get \"error.message\")\n\nif [[ -n \"$ERROR_CODE\" || \"$HTTP_STATUS\" -ge 400 ]]; then\n  echo \"ERROR: Vercel API returned an error (HTTP $HTTP_STATUS).\" >&2\n  [[ -n \"$ERROR_CODE\" ]] && echo \"  Code   : $ERROR_CODE\" >&2\n  [[ -n \"$ERROR_MSG\" ]] && echo \"  Message: $ERROR_MSG\" >&2\n  exit 1\nfi\n\n# Extract deployment details\n\nDEPLOY_ID=$(echo \"$RESPONSE\" | json_get \"id\")\nDEPLOY_URL=$(echo \"$RESPONSE\" | json_get \"url\")\nDEPLOY_STATE=$(echo \"$RESPONSE\" | json_get \"readyState\")\n\nif [[ -z \"$DEPLOY_ID\" ]]; then\n  echo \"ERROR: Could not read the deployment id from the Vercel API response.\" >&2\n  exit 1\nfi\n\necho \"Deployment created.\"\necho \"  ID    : $DEPLOY_ID\"\necho \"  State : $DEPLOY_STATE\"\necho \"  URL   : https://$DEPLOY_URL\"\n\nset_octopusvariable \"DeploymentId\" \"$DEPLOY_ID\"\nset_octopusvariable \"DeploymentUrl\" \"https://$DEPLOY_URL\"\nset_octopusvariable \"DeploymentState\" \"$DEPLOY_STATE\"\n\nif [[ \"$WaitForDeployment\" != \"True\" ]]; then\n  echo \"\"\n  echo \"Wait for deployment is disabled; not waiting for the build to finish.\"\n  exit 0\nfi\n\n# Poll until the deployment is READY, fails, or the timeout is reached\n\nSTATUS_URL=\"https://api.vercel.com/v13/deployments/${DEPLOY_ID}\"\nif [[ -n \"$TeamId\" ]]; then\n  STATUS_URL=\"${STATUS_URL}?teamId=${TeamId}\"\nfi\n\necho \"\"\necho \"Waiting up to ${TimeoutSeconds}s for the deployment to become READY...\"\n\nINTERVAL=10\nELAPSED=0\n\nwhile true; do\n  STATUS_RAW=$(curl --silent --show-error --max-time 60 \\\n    --header \"Authorization: Bearer $ApiToken\" \\\n    \"$STATUS_URL\")\n\n  if [[ $? -ne 0 ]]; then\n    echo \"WARNING: Status check failed; retrying...\" >&2\n  else\n    DEPLOY_STATE=$(echo \"$STATUS_RAW\" | json_get \"readyState\")\n    echo \"  [${ELAPSED}s] State: ${DEPLOY_STATE:-UNKNOWN}\"\n\n    case \"$DEPLOY_STATE\" in\n      READY)\n        set_octopusvariable \"DeploymentState\" \"$DEPLOY_STATE\"\n        echo \"\"\n        echo \"Deployment is READY: https://$DEPLOY_URL\"\n        exit 0\n        ;;\n      ERROR|CANCELED)\n        set_octopusvariable \"DeploymentState\" \"$DEPLOY_STATE\"\n        echo \"ERROR: Deployment finished in state $DEPLOY_STATE. Check the build logs in the Vercel dashboard.\" >&2\n        exit 1\n        ;;\n    esac\n  fi\n\n  if (( ELAPSED >= TimeoutSeconds )); then\n    set_octopusvariable \"DeploymentState\" \"${DEPLOY_STATE:-TIMED_OUT}\"\n    echo \"ERROR: Timed out after ${TimeoutSeconds}s waiting for the deployment to become READY (last state: ${DEPLOY_STATE:-unknown}).\" >&2\n    exit 1\n  fi\n\n  sleep \"$INTERVAL\"\n  ELAPSED=$((ELAPSED + INTERVAL))\ndone"
  },
  "Category": "Vercel",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates/vercel-deploy.json",
  "Website": "/step-templates/1f76f2e4-7f3b-4eb1-8278-6503fcd78dcd",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAADMElEQVR42u3dTYrDMBAFYd3MR/PRfDQHBxKMyI8S4lit/h4UzGJWHoqujZhSSlkBPMVHAAgCEAQgCEAQgCAAQQCCAAQBCAKAIABBAIIABAEIAhAEIAhAEIAgAEEAEAQgCEAQgCAAQQCCAAQBCAIQBABBAIIABAEIAhAEIAhAEIAgAEEAggAgCEAQgCAAQQCCAAQBCAIQBCAITmCapiu+BUHwgNt8C4KgYlmWuyDbz74JQbBLq3pSiyCo0qqeb0MQabVLq3pSiyDS6s2kFkFcjxdzRQiSknme19Ztv+ubEURaSS2CoC2tpBZBpNWHk1oEkVZSiyDS6vtJLYJIK6lFEGkltQiCn6WV1CKItJJaBJFWUosgOCytpBZBXA9XhCBZH0EdNd+cINJKahFEWkktgkgrqUUQaXXGpBZBpJXUIoi0kloEkVZSiyDSSmoRRFp1On8jgkgrqUUQaSW1COJ6uCIE8QjqjHlcRRBpJbUIIq2kFkGkldQiiLSSWgSRVlKLINJKahFEWg03qUUQaSW1CCKtpBZBpJXUIoi0kloEcT1cEYJ4BBVv/vYEkVZSiyDSSmoRRFpJLYJIK6lFEGkltQgiraQWQaSV1CKItJJaBJFWJrUyCyKtpBZBpJXUIojr4YoQxCOoDpb0cZW0MqlFEGkltQgiraQWQaSV1CKItJJaBJFWUosg0kpqEURaSS2CSCtLmFrSyqRWKkGkldQiiOvhihDEI6hoI4i0slypJa1Mag0viEktgkgrqUUQaSW1CCKtpBZBpJUNkFrSyqTWcIKY1CKItJJaBJFWUosgrodFvSIeQdn/FvBxlbQyqRVeEGkltQgiraQWQaSVhUotaWVSK6Qg0kpqEURaSa2+U0tamdQKJYi0kloEkVYWI7WklUmtEIK4HtbhFfEIyvoaQaSVxUktaWVSq2tBzDpOLWllUiv1v4EGCAIQBCAIQBCAIABBAIIABAFAEIAgAEEAggAEAQgCEAQgCEAQAD4AQBCAIABBAIIABAEIAhAEIAhAEAAEAQgCEAQgCEAQgCAAQQCCAAQBCOIjAAQBCAIQBCAIQBCAIABBAIIABAFAEKCBC39pPcvCug+iAAAAAElFTkSuQmCC",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Monday, July 20, 2026