CI/CD Integration with Toggly Release Tracker
This guide explains how to integrate any CI/CD system (Azure DevOps, GitHub Actions, GitLab CI, Jenkins, CircleCI, etc.) with Toggly's Release Tracker feature via HTTP APIs.
Table of Contents
Concepts
Release
A Release is a collection of feature flag changes that are managed together. Each release has:
- Status:
Draft,Pending,Live,RolledBack, orFailed - Feature Changes: The feature flags that will be modified when the release is activated
- CI Links: Associations with CI/CD builds
- Gates: Automated checks that must pass before activation
- Environment: The target environment (e.g., "Production", "Staging")
Build Association
Build Association links a CI/CD build to a release. This allows you to:
- Track which builds are part of a release
- View build information (branch, commit SHA, build number) in the release
- Automatically find or create releases based on build criteria
Deployment Events
Deployment Events notify Toggly about deployment lifecycle events:
deployment_started: Deployment has begundeployment_completed: Deployment finished successfullydeployment_failed: Deployment failed
These events can trigger gate evaluations automatically.
Gates and Gate Status
Gates are automated checks that validate whether a release is safe to activate. Gate types include:
- Metrics: Check if metrics meet thresholds (e.g., error rate < 1%)
- Anomaly: Detect anomalies in metrics
- Manual: Require manual approval
- External: Call external webhooks/APIs for validation
Gate status can be:
pending: Gate hasn't been evaluated yet or is waitingpassed: Gate check passedfailed: Gate check failed
Activate / Rollback
- Activate: Applies the feature flag changes defined in the release to the target environment. Captures the current state as
fromStatefor potential rollback. - Rollback: Restores feature flags to their previous state (
fromState) before the release was activated.
Authentication
All API endpoints require authentication using a Bearer token (JWT).
Obtaining an API Token
- Log in to Toggly
- Navigate to your Team settings
- Create an API credential with the
apiAccessclaim - Copy the generated JWT token
Using the Token
Include the token in the Authorization header:
Authorization: Bearer <your-jwt-token>
Permission Scope
API tokens are scoped to:
- Team: The token must have access to the team that owns the application
- Application: Access is limited to applications within the authorized team
- Environment: Access is limited to environments within the authorized application
Ensure your API token has the apiAccess: true claim and is associated with the correct team.
API Endpoints
All endpoints are under the /api/releases base path and require the ReleaseTracker feature flag to be enabled.
1. Associate Build with Release
Associates a CI/CD build with a release. If no matching draft release exists, creates a new one.
Endpoint: POST /api/releases/associate-build
Request Body:
{
"projectKey": "my-app",
"environment": "Production",
"ciProvider": "github",
"build": {
"runId": "123456",
"runUrl": "https://github.com/org/repo/actions/runs/123456",
"pipelineName": "deploy-production",
"branch": "main",
"commitSha": "abc123def456",
"buildNumber": "1.2.3"
},
"mode": "use-latest-draft-or-create",
"matchCriteria": {
"byBranch": true
},
"releaseTemplateKey": "default",
"createOptions": {
"namePattern": "Release ${branch} - ${buildNumber}"
}
}
Request Fields:
projectKey(required): Application ID or Nameenvironment(required): Environment name (e.g., "Production", "Staging")ciProvider(required): CI provider identifier (e.g., "azure-devops", "github", "gitlab", "jenkins", "circleci")build(required): Build information objectrunId(required): Unique CI run/build IDrunUrl(optional): URL to view the build in CI systempipelineName(required): Name of the pipeline/workflowbranch(optional): Git branch namecommitSha(optional): Git commit SHAbuildNumber(optional): Build number/version
mode(optional): How to find/create release. Default:"use-latest-draft-or-create"matchCriteria(optional): Criteria for matching existing releasesbyBranch: Match releases by branch namebyService: Match releases by service/project
releaseTemplateKey(optional): Template key to use when creating new releasecreateOptions(optional): Options for creating new releasesnamePattern: Handlebars-style pattern (supports${branch},${buildNumber},${commitSha})
Response (200 OK):
{
"releaseId": "abc123",
"releaseUrl": "https://toggly.io/releases/abc123"
}
Response Fields:
releaseId: The release ID (use this for subsequent API calls)releaseUrl: URL to view the release in Toggly UI
Error Responses:
400 Bad Request: Validation errors (missing required fields, invalid environment, etc.)401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the application/team404 Not Found: Application not found
2. Get Release Details
Retrieves full details about a release, including feature changes, CI links, gate status, and deployment events.
Endpoint: GET /api/releases/{releaseId}
Path Parameters:
releaseId: The release ID (fromassociate-buildresponse)
Response (200 OK):
{
"id": "abc123",
"applicationId": "app-456",
"environment": "Production",
"name": "Release main - 1.2.3",
"status": "Draft",
"releaseNotes": "Deploying new features",
"featureChanges": [
{
"flagKey": "new-feature",
"fromState": null,
"toState": [
{
"name": "AlwaysOn",
"parameters": null
}
]
}
],
"ciLinks": [
{
"provider": "github",
"pipelineName": "deploy-production",
"runId": "123456",
"runUrl": "https://github.com/org/repo/actions/runs/123456",
"branch": "main",
"commitSha": "abc123def456",
"buildNumber": "1.2.3"
}
],
"gateStatus": {
"releaseId": "abc123",
"overallStatus": "pending",
"gates": [
{
"gateId": "gate-1",
"name": "Error Rate Check",
"status": "pending",
"lastCheckedAt": null,
"details": null
}
]
},
"latestGateEvaluations": [],
"latestDeploymentEvents": [],
"createdBy": "user-789",
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z",
"activatedAt": null,
"rolledBackAt": null
}
Response Fields:
id: Release IDapplicationId: Application IDenvironment: Environment namename: Release namestatus: Release status (Draft,Pending,Live,RolledBack,Failed)releaseNotes: Optional release notesfeatureChanges: List of feature flag changesciLinks: List of associated CI buildsgateStatus: Current gate status summarylatestGateEvaluations: Recent gate evaluation resultslatestDeploymentEvents: Recent deployment eventscreatedBy: User ID who created the releasecreatedAt: Creation timestampupdatedAt: Last update timestampactivatedAt: Activation timestamp (if activated)rolledBackAt: Rollback timestamp (if rolled back)
Error Responses:
401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the release404 Not Found: Release not found
3. Notify Deployment Event
Notifies Toggly about a deployment lifecycle event. This can trigger automatic gate evaluations.
Endpoint: POST /api/releases/{releaseId}/events
Path Parameters:
releaseId: The release ID
Request Body:
{
"eventType": "deployment_completed",
"provider": "github",
"environment": "Production",
"metadata": {
"deploymentId": "deploy-123",
"duration": 300,
"instanceCount": 3
}
}
Request Fields:
eventType(required): Event type -"deployment_started","deployment_completed", or"deployment_failed"provider(required): CI provider identifierenvironment(required): Environment namemetadata(optional): Additional event metadata as key-value pairs
Response (200 OK):
Returns the updated gate status (same format as GET /api/releases/{releaseId}/gates/status):
{
"releaseId": "abc123",
"overallStatus": "passed",
"gates": [
{
"gateId": "gate-1",
"name": "Error Rate Check",
"status": "passed",
"lastCheckedAt": "2024-01-15T10:05:00Z",
"details": {
"errorRate": 0.005,
"threshold": 0.01
}
}
]
}
Error Responses:
400 Bad Request: Invalid event type or validation errors401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the release404 Not Found: Release not found
4. Get Gates Status
Retrieves the current status of all gates for a release.
Endpoint: GET /api/releases/{releaseId}/gates/status
Path Parameters:
releaseId: The release ID
Response (200 OK):
{
"releaseId": "abc123",
"overallStatus": "pending",
"gates": [
{
"gateId": "gate-1",
"name": "Error Rate Check",
"status": "pending",
"lastCheckedAt": null,
"details": null
},
{
"gateId": "gate-2",
"name": "Manual Approval",
"status": "pending",
"lastCheckedAt": null,
"details": null
}
]
}
Response Fields:
releaseId: Release IDoverallStatus: Overall gate status -"pending","passed", or"failed"gates: List of individual gate statusesgateId: Gate identifiername: Gate namestatus: Gate status -"pending","passed", or"failed"lastCheckedAt: Timestamp of last evaluationdetails: Additional evaluation details (JSON object)
Error Responses:
401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the release404 Not Found: Release not found
5. Activate Release
Activates a release by applying all feature flag changes to the target environment. Captures the current state as fromState for potential rollback.
Endpoint: POST /api/releases/{releaseId}/activate
Path Parameters:
releaseId: The release ID
Request Body: None (empty body)
Response (200 OK):
Returns the updated release model (same format as GET /api/releases/{releaseId}):
{
"id": "abc123",
"applicationId": "app-456",
"environment": "Production",
"name": "Release main - 1.2.3",
"status": "Live",
"activatedAt": "2024-01-15T10:10:00Z",
...
}
Error Responses:
400 Bad Request: Release is not in a valid state for activation (must beDraftorPending)401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the release404 Not Found: Release or application environment not found
Important Notes:
- Activation captures the current feature flag state as
fromStatefor each changed flag - Once activated, the release status changes to
Live - Feature flags are immediately updated in the target environment
6. Rollback Release
Rolls back a release by restoring feature flags to their previous state (fromState).
Endpoint: POST /api/releases/{releaseId}/rollback
Path Parameters:
releaseId: The release ID
Request Body: None (empty body)
Response (200 OK):
Returns the updated release model:
{
"id": "abc123",
"status": "RolledBack",
"rolledBackAt": "2024-01-15T10:15:00Z",
...
}
Error Responses:
400 Bad Request: Release is not in a valid state for rollback (must beLive)401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the release404 Not Found: Release or application environment not found
Important Notes:
- Rollback only works for releases in
Livestatus - If a feature flag was modified by another release after activation, a conflict is logged but rollback still proceeds
- Once rolled back, the release status changes to
RolledBack
Integration Examples
Example 1: Generic curl-based Integration
This example shows how to integrate using shell scripts and curl commands.
#!/bin/bash
# Configuration
TOGGLY_API_URL="https://api.toggly.io"
TOGGLY_API_TOKEN="your-jwt-token-here"
PROJECT_KEY="my-app"
ENVIRONMENT="Production"
CI_PROVIDER="github"
BUILD_RUN_ID="${GITHUB_RUN_ID}"
BUILD_RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
PIPELINE_NAME="deploy-production"
BRANCH="${GITHUB_REF#refs/heads/}"
COMMIT_SHA="${GITHUB_SHA}"
BUILD_NUMBER="${GITHUB_RUN_NUMBER}"
# Step 1: Associate build with release
echo "Associating build with release..."
ASSOCIATE_RESPONSE=$(curl -s -X POST "${TOGGLY_API_URL}/api/releases/associate-build" \
-H "Authorization: Bearer ${TOGGLY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"projectKey\": \"${PROJECT_KEY}\",
\"environment\": \"${ENVIRONMENT}\",
\"ciProvider\": \"${CI_PROVIDER}\",
\"build\": {
\"runId\": \"${BUILD_RUN_ID}\",
\"runUrl\": \"${BUILD_RUN_URL}\",
\"pipelineName\": \"${PIPELINE_NAME}\",
\"branch\": \"${BRANCH}\",
\"commitSha\": \"${COMMIT_SHA}\",
\"buildNumber\": \"${BUILD_NUMBER}\"
},
\"mode\": \"use-latest-draft-or-create\",
\"matchCriteria\": {
\"byBranch\": true
}
}")
# Extract release ID
RELEASE_ID=$(echo "${ASSOCIATE_RESPONSE}" | jq -r '.releaseId')
echo "Release ID: ${RELEASE_ID}"
# Step 2: Deploy application (your deployment logic here)
echo "Deploying application..."
# ... your deployment commands ...
# Step 3: Notify deployment started
echo "Notifying deployment started..."
curl -s -X POST "${TOGGLY_API_URL}/api/releases/${RELEASE_ID}/events" \
-H "Authorization: Bearer ${TOGGLY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"eventType\": \"deployment_started\",
\"provider\": \"${CI_PROVIDER}\",
\"environment\": \"${ENVIRONMENT}\"
}" > /dev/null
# Step 4: Wait for deployment to complete
# ... your deployment wait logic ...
# Step 5: Notify deployment completed
echo "Notifying deployment completed..."
curl -s -X POST "${TOGGLY_API_URL}/api/releases/${RELEASE_ID}/events" \
-H "Authorization: Bearer ${TOGGLY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"eventType\": \"deployment_completed\",
\"provider\": \"${CI_PROVIDER}\",
\"environment\": \"${ENVIRONMENT}\",
\"metadata\": {
\"duration\": 300,
\"instanceCount\": 3
}
}" > /dev/null
# Step 6: Poll gate status until passed or failed
echo "Waiting for gates to pass..."
MAX_ATTEMPTS=30
ATTEMPT=0
GATE_STATUS="pending"
while [ "${GATE_STATUS}" = "pending" ] && [ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]; do
sleep 10
ATTEMPT=$((ATTEMPT + 1))
GATE_RESPONSE=$(curl -s -X GET "${TOGGLY_API_URL}/api/releases/${RELEASE_ID}/gates/status" \
-H "Authorization: Bearer ${TOGGLY_API_TOKEN}")
GATE_STATUS=$(echo "${GATE_RESPONSE}" | jq -r '.overallStatus')
echo "Gate status (attempt ${ATTEMPT}): ${GATE_STATUS}"
done
# Step 7: Activate or rollback based on gate status
if [ "${GATE_STATUS}" = "passed" ]; then
echo "Gates passed! Activating release..."
curl -s -X POST "${TOGGLY_API_URL}/api/releases/${RELEASE_ID}/activate" \
-H "Authorization: Bearer ${TOGGLY_API_TOKEN}" \
-H "Content-Type: application/json" > /dev/null
echo "Release activated successfully!"
exit 0
elif [ "${GATE_STATUS}" = "failed" ]; then
echo "Gates failed! Rolling back release..."
curl -s -X POST "${TOGGLY_API_URL}/api/releases/${RELEASE_ID}/rollback" \
-H "Authorization: Bearer ${TOGGLY_API_TOKEN}" \
-H "Content-Type: application/json" > /dev/null
echo "Release rolled back!"
exit 1
else
echo "Gates still pending after ${MAX_ATTEMPTS} attempts. Manual intervention required."
exit 2
fi
Example 2: Pseudo-code CI Integration Flow
This example shows a high-level integration flow that can be adapted to any CI/CD system.
# Pseudo-code for CI/CD integration
import requests
import time
class TogglyReleaseTracker:
def __init__(self, api_url, api_token):
self.api_url = api_url
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
def associate_build(self, project_key, environment, ci_provider, build_info):
"""Associate CI build with a release (creates new if needed)"""
url = f"{self.api_url}/api/releases/associate-build"
payload = {
"projectKey": project_key,
"environment": environment,
"ciProvider": ci_provider,
"build": build_info,
"mode": "use-latest-draft-or-create",
"matchCriteria": {
"byBranch": True
}
}
response = requests.post(url, json=payload, headers=self.headers)
response.raise_for_status()
return response.json()["releaseId"]
def notify_deployment_event(self, release_id, event_type, provider, environment, metadata=None):
"""Notify Toggly about a deployment event"""
url = f"{self.api_url}/api/releases/{release_id}/events"
payload = {
"eventType": event_type,
"provider": provider,
"environment": environment,
"metadata": metadata or {}
}
response = requests.post(url, json=payload, headers=self.headers)
response.raise_for_status()
return response.json()
def get_gate_status(self, release_id):
"""Get current gate status for a release"""
url = f"{self.api_url}/api/releases/{release_id}/gates/status"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json()
def activate_release(self, release_id):
"""Activate a release (apply feature flag changes)"""
url = f"{self.api_url}/api/releases/{release_id}/activate"
response = requests.post(url, headers=self.headers)
response.raise_for_status()
return response.json()
def rollback_release(self, release_id):
"""Rollback a release (restore previous feature flag state)"""
url = f"{self.api_url}/api/releases/{release_id}/rollback"
response = requests.post(url, headers=self.headers)
response.raise_for_status()
return response.json()
# Main integration flow
def main():
# Initialize tracker
tracker = TogglyReleaseTracker(
api_url="https://api.toggly.io",
api_token=os.environ["TOGGLY_API_TOKEN"]
)
# Get CI/CD build information from environment variables
build_info = {
"runId": os.environ["CI_RUN_ID"],
"runUrl": os.environ["CI_RUN_URL"],
"pipelineName": os.environ["CI_PIPELINE_NAME"],
"branch": os.environ["CI_BRANCH"],
"commitSha": os.environ["CI_COMMIT_SHA"],
"buildNumber": os.environ["CI_BUILD_NUMBER"]
}
# Step 1: Associate build with release
print("Associating build with release...")
release_id = tracker.associate_build(
project_key="my-app",
environment="Production",
ci_provider="github",
build_info=build_info
)
print(f"Release ID: {release_id}")
# Step 2: Deploy application
print("Deploying application...")
deploy_application() # Your deployment logic
# Step 3: Notify deployment started
print("Notifying deployment started...")
tracker.notify_deployment_event(
release_id=release_id,
event_type="deployment_started",
provider="github",
environment="Production"
)
# Step 4: Wait for deployment to complete
wait_for_deployment_completion()
# Step 5: Notify deployment completed
print("Notifying deployment completed...")
gate_status_response = tracker.notify_deployment_event(
release_id=release_id,
event_type="deployment_completed",
provider="github",
environment="Production",
metadata={
"duration": 300,
"instanceCount": 3
}
)
# Step 6: Poll gate status until passed/failed
print("Waiting for gates to pass...")
max_attempts = 30
attempt = 0
overall_status = "pending"
while overall_status == "pending" and attempt < max_attempts:
time.sleep(10) # Wait 10 seconds between checks
attempt += 1
gate_status = tracker.get_gate_status(release_id)
overall_status = gate_status["overallStatus"]
print(f"Gate status (attempt {attempt}): {overall_status}")
# Print individual gate statuses
for gate in gate_status["gates"]:
print(f" - {gate['name']}: {gate['status']}")
# Step 7: Activate or rollback based on gate status
if overall_status == "passed":
print("Gates passed! Activating release...")
tracker.activate_release(release_id)
print("Release activated successfully!")
return 0
elif overall_status == "failed":
print("Gates failed! Rolling back release...")
tracker.rollback_release(release_id)
print("Release rolled back!")
return 1
else:
print(f"Gates still pending after {max_attempts} attempts. Manual intervention required.")
return 2
if __name__ == "__main__":
exit(main())
Additional Notes
Release Templates
When creating a new release via associate-build, you can specify a releaseTemplateKey to use predefined gate configurations and naming patterns. Templates are configured in the Toggly UI.
Gate Evaluation Timing
- Gates with
trigger: OnDeploymentCompletedare automatically evaluated when you send adeployment_completedevent - Manual gates require approval via the Toggly UI
- You can poll
gates/statusto check gate evaluation progress
Error Handling
All endpoints return standard HTTP status codes:
200 OK: Success400 Bad Request: Validation errors (check response body for details)401 Unauthorized: Missing or invalid authentication token403 Forbidden: Token doesn't have access to the resource404 Not Found: Resource not found500 Internal Server Error: Server error
Always check the response status code and handle errors appropriately in your integration.
Rate Limiting
Toggly may implement rate limiting on API endpoints. If you receive 429 Too Many Requests, implement exponential backoff and retry logic.
Feature Flag State Management
- When a release is activated, the current state of each feature flag is captured as
fromState - The
toStateis then applied to the environment - On rollback,
fromStateis restored - If a feature flag is modified by another release after activation, rollback will still proceed but may log conflicts