Automate TeamCity with the REST API — authentication, all key endpoints, triggering builds, downloading artifacts, querying test results, managing agents, and practical curl-based scripts
| Attribute | Value |
|---|---|
| Base URL | http://SERVER:8111/app/rest (or https://...) |
| API version in 2017.2.2 | v10 (default), v9 (legacy). Use /app/rest/10.0/ for explicit versioning. |
| Response format | JSON (default) or XML. Use Accept: application/json header. |
| Content type for POST/PUT | Content-Type: application/json |
TeamCity REST API uses locators — flexible filter expressions — to identify resources:
# Single dimension locator:
builds?locator=buildType:BackendServices_Build
# Multi-dimension locator (comma-separated):
builds?locator=buildType:BackendServices_Build,branch:main,status:SUCCESS
# Dimensions:
# id:N — by internal ID
# number:N — by build number
# buildType:ID — by build config ID
# project:ID — filter by project
# status:SUCCESS|FAILURE|ERROR|UNKNOWN
# branch:name — by branch name
# running:true — only running builds
# personal:false — exclude personal builds
# count:N — limit results
# Method 1: Bearer token (recommended — from Phase 13)
TC_TOKEN="your-api-token"
TC_URL="https://ci.yourcompany.com"
curl -H "Authorization: Bearer $TC_TOKEN" \
-H "Accept: application/json" \
"$TC_URL/app/rest/server"
# Method 2: HTTP Basic with username:password
curl -u "username:password" \
-H "Accept: application/json" \
"$TC_URL/app/rest/server"
# Method 3: Guest (read-only, no credentials)
curl -H "Accept: application/json" \
"$TC_URL/guestAuth/app/rest/builds?locator=count:5"
API tokens (Bearer auth) are the safest option: they can be revoked without changing the user's password, they bypass 2FA (acceptable for automation), and they show which token was used in the audit log. Create a dedicated service account (ci-bot) and generate tokens under it.
# List all projects
GET /app/rest/projects
# Get a specific project
GET /app/rest/projects/id:BackendServices
# List build configs in a project
GET /app/rest/projects/id:BackendServices/buildTypes
# Create a project (POST)
POST /app/rest/projects
Content-Type: application/json
{
"name": "NewService",
"id": "NewService",
"parentProject": { "id": "BackendServices" }
}
# Delete a project
DELETE /app/rest/projects/id:OldProject
# List all build configurations
GET /app/rest/buildTypes
# Get a specific build configuration
GET /app/rest/buildTypes/id:BackendServices_Build
# List parameters of a build config
GET /app/rest/buildTypes/id:BackendServices_Build/parameters
# Set a parameter on a build config
PUT /app/rest/buildTypes/id:BackendServices_Build/parameters/env.NEXUS_URL
Content-Type: text/plain
https://nexus.yourcompany.com
# List last 10 builds for a build config
GET /app/rest/builds?locator=buildType:BackendServices_Build,count:10
# Get a specific build
GET /app/rest/builds/id:12345
GET /app/rest/builds/buildType:BackendServices_Build,number:42
# Get last successful build on main branch
GET /app/rest/builds?locator=buildType:BackendServices_Build,branch:main,status:SUCCESS,count:1
# Get all running builds
GET /app/rest/builds?locator=running:true
# Get builds for a specific branch
GET /app/rest/builds?locator=buildType:BackendServices_Build,branch:(name:refs/heads/feature/login)
# Example response fields:
# id, number, status, state, branch, startDate, finishDate, duration,
# buildType, changes, artifacts, testOccurrences, revisions
# Trigger a build (POST to buildQueue)
POST /app/rest/buildQueue
Content-Type: application/json
Accept: application/json
{
"buildType": { "id": "BackendServices_Build" }
}
# Trigger on a specific branch
{
"buildType": { "id": "BackendServices_Build" },
"branchName": "refs/heads/feature/login"
}
# Trigger with custom parameters
{
"buildType": { "id": "BackendServices_Deploy" },
"branchName": "refs/heads/main",
"properties": {
"property": [
{ "name": "deploy.environment", "value": "staging" },
{ "name": "deploy.version", "value": "1.2.42" }
]
}
}
# Response: build object with id, number, status=queued
# Use the returned id to poll for completion:
# Poll build status
while true; do
STATUS=$(curl -sH "Authorization: Bearer $TC_TOKEN" \
"$TC_URL/app/rest/builds/id:$BUILD_ID" | jq -r '.status')
STATE=$(curl -sH "Authorization: Bearer $TC_TOKEN" \
"$TC_URL/app/rest/builds/id:$BUILD_ID" | jq -r '.state')
echo "State: $STATE Status: $STATUS"
[ "$STATE" = "finished" ] && break
sleep 10
done
POST to /app/rest/buildQueue to trigger a build. The build starts as "queued," then becomes "running," then "finished." GET from /app/rest/builds to query finished builds. GET from /app/rest/buildQueue to see builds still waiting for an agent. Confusing these is a common scripting mistake.
# Pin a build (prevent cleanup from deleting it)
PUT /app/rest/builds/id:12345/pin
Content-Type: text/plain
Pinned for release 1.2.0
# Unpin
DELETE /app/rest/builds/id:12345/pin
# Add a tag to a build
POST /app/rest/builds/id:12345/tags
Content-Type: application/json
{ "tag": [{ "name": "release" }, { "name": "v1.2.0" }] }
# Add a build comment
PUT /app/rest/builds/id:12345/comment
Content-Type: text/plain
Deployed to production by ops team at 14:00 UTC
# List artifacts for a build
GET /app/rest/builds/id:12345/artifacts
# Download a specific artifact file
GET /app/rest/builds/id:12345/artifacts/content/path/to/myapp.jar
# Returns the file content directly (binary)
# Download last successful build's artifact
GET /app/rest/builds/buildType:BackendServices_Build,status:SUCCESS,branch:main,count:1/artifacts/content/artifacts/myapp.jar
# Download as file (curl)
curl -H "Authorization: Bearer $TC_TOKEN" \
-o myapp.jar \
"$TC_URL/app/rest/builds/buildType:BackendServices_Build,status:SUCCESS,count:1/artifacts/content/artifacts/myapp.jar"
# Alternative: direct download URL (no API version in path)
http://SERVER:8111/repository/download/BackendServices_Build/.lastSuccessful/artifacts/myapp.jar
# List all agents
GET /app/rest/agents
# List unauthorized agents
GET /app/rest/agents?locator=authorized:false
# Authorize an agent
PUT /app/rest/agents/id:5/authorized
Content-Type: text/plain
true
# Disable an agent (takes it out of service, won't receive new builds)
PUT /app/rest/agents/id:5/enabled
Content-Type: text/plain
false
# Get agent details (including system properties reported)
GET /app/rest/agents/id:5
# Get test results for a specific build
GET /app/rest/testOccurrences?locator=build:12345
# Get only failed tests
GET /app/rest/testOccurrences?locator=build:12345,status:FAILURE
# Get muted tests
GET /app/rest/testOccurrences?locator=muted:true
# Get test history (all occurrences of a specific test)
GET /app/rest/testOccurrences?locator=test:name:com.example.UserServiceTest.testLogin
# Response fields: name, status, duration, details (stacktrace for failures)
# Get changes (VCS commits) included in a build
GET /app/rest/changes?locator=build:12345
# Get changes for a build type since last successful build
GET /app/rest/changes?locator=buildType:BackendServices_Build,sinceChange:(buildType:BackendServices_Build,status:SUCCESS,count:1)
# Get a specific change
GET /app/rest/changes/id:678
# Returns: commit SHA, author, date, comment, files changed
#!/bin/bash
# trigger-and-wait.sh — triggers a build and waits for completion
set -euo pipefail
TC_URL="${1:-https://ci.yourcompany.com}"
BUILD_TYPE="${2:-BackendServices_Build}"
BRANCH="${3:-refs/heads/main}"
TC_TOKEN="${TC_TOKEN:?TC_TOKEN environment variable required}"
echo "Triggering $BUILD_TYPE on $BRANCH..."
RESPONSE=$(curl -sf -X POST \
-H "Authorization: Bearer $TC_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
"$TC_URL/app/rest/buildQueue" \
-d "{\"buildType\":{\"id\":\"$BUILD_TYPE\"},\"branchName\":\"$BRANCH\"}")
BUILD_ID=$(echo "$RESPONSE" | jq -r '.id')
echo "Build queued: ID=$BUILD_ID"
# Poll until finished
while true; do
BUILD=$(curl -sf \
-H "Authorization: Bearer $TC_TOKEN" \
-H "Accept: application/json" \
"$TC_URL/app/rest/builds/id:$BUILD_ID")
STATE=$(echo "$BUILD" | jq -r '.state')
STATUS=$(echo "$BUILD" | jq -r '.status')
echo " State: $STATE | Status: $STATUS"
[ "$STATE" = "finished" ] && break
sleep 15
done
echo "Build #$(echo "$BUILD" | jq -r '.number') finished: $STATUS"
[ "$STATUS" = "SUCCESS" ] && exit 0 || exit 1
#!/bin/bash
# download-latest.sh — downloads the latest artifact from a build config
TC_URL="https://ci.yourcompany.com"
BUILD_TYPE="BackendServices_Build"
ARTIFACT_PATH="artifacts/myapp.jar"
OUTPUT="myapp.jar"
curl -sf \
-H "Authorization: Bearer $TC_TOKEN" \
-o "$OUTPUT" \
"$TC_URL/app/rest/builds/buildType:${BUILD_TYPE},status:SUCCESS,branch:main,count:1/artifacts/content/${ARTIFACT_PATH}"
echo "Downloaded: $OUTPUT ($(du -sh "$OUTPUT" | cut -f1))"