api
GitLab Duo API - Programmatic AI Integration
Overview
The GitLab Duo API provides programmatic access to AI features, enabling custom integrations, automation, and workflow extensions. Integrate AI capabilities into your tools, scripts, and applications.
Authentication
Personal Access Tokens
# Create token with api scope GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx" # Use in requests curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/..."
OAuth2
import requests from requests_oauthlib import OAuth2Session oauth = OAuth2Session( client_id='your_client_id', redirect_uri='http://localhost:8080/callback' ) authorization_url, state = oauth.authorization_url( 'https://gitlab.com/oauth/authorize' ) # After user authorization token = oauth.fetch_token( 'https://gitlab.com/oauth/token', client_secret='your_client_secret', authorization_response=callback_url )
CI/CD Tokens
# .gitlab-ci.yml duo:api: script: - | curl --header "PRIVATE-TOKEN: $CI_JOB_TOKEN" \ "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/duo/suggestions"
Code Suggestions API
Get Code Suggestions
Endpoint: POST /api/v4/duo/code_suggestions
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "project_id": 12345, "file_path": "src/auth.py", "current_file": "def login(username, password):\n # ", "cursor_position": {"line": 2, "character": 6}, "language": "python" }' \ "https://gitlab.com/api/v4/duo/code_suggestions"
Response:
{ "suggestions": [ { "text": "user = User.query.filter_by(username=username).first()\n if user and user.check_password(password):\n return create_session(user)\n return None", "range": { "start": {"line": 2, "character": 6}, "end": {"line": 2, "character": 6} }, "confidence": 0.92 } ], "model": "claude-sonnet-4", "latency_ms": 234 }
Python Client:
import requests def get_code_suggestions( project_id: int, file_path: str, content: str, cursor_line: int, cursor_char: int, language: str ) -> dict: """Get AI code suggestions""" url = "https://gitlab.com/api/v4/duo/code_suggestions" headers = { "PRIVATE-TOKEN": os.getenv("GITLAB_TOKEN"), "Content-Type": "application/json" } payload = { "project_id": project_id, "file_path": file_path, "current_file": content, "cursor_position": { "line": cursor_line, "character": cursor_char }, "language": language } response = requests.post(url, json=payload, headers=headers) response.raise_for_status() return response.json() # Usage suggestions = get_code_suggestions( project_id=12345, file_path="src/auth.py", content="def login(username, password):\n # ", cursor_line=2, cursor_char=6, language="python" ) print(suggestions["suggestions"][0]["text"])
Stream Code Suggestions
For real-time suggestions:
import sseclient import requests def stream_code_suggestions(project_id: int, content: str): """Stream code suggestions in real-time""" url = "https://gitlab.com/api/v4/duo/code_suggestions/stream" headers = { "PRIVATE-TOKEN": os.getenv("GITLAB_TOKEN"), "Accept": "text/event-stream" } payload = { "project_id": project_id, "current_file": content } response = requests.post( url, json=payload, headers=headers, stream=True ) client = sseclient.SSEClient(response) for event in client.events(): data = json.loads(event.data) yield data["token"] # Usage for token in stream_code_suggestions(12345, "def calculate"): print(token, end="", flush=True)
Chat API
Send Chat Message
Endpoint: POST /api/v4/duo/chat
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "project_id": 12345, "message": "Explain how JWT authentication works", "context": { "files": ["src/auth.py", "src/middleware/jwt.py"], "issue_iid": 123 } }' \ "https://gitlab.com/api/v4/duo/chat"
Response:
{ "response": "JWT (JSON Web Token) authentication works by...", "sources": [ { "type": "file", "path": "src/auth.py", "lines": [15, 32] } ], "model": "claude-sonnet-4", "conversation_id": "conv_abc123" }
Python Client:
class GitLabDuoChat: """GitLab Duo Chat API client""" def __init__(self, token: str, base_url: str = "https://gitlab.com"): self.token = token self.base_url = base_url self.conversation_id = None def send_message( self, message: str, project_id: int, context: dict = None ) -> dict: """Send message to Duo Chat""" url = f"{self.base_url}/api/v4/duo/chat" headers = { "PRIVATE-TOKEN": self.token, "Content-Type": "application/json" } payload = { "project_id": project_id, "message": message } if self.conversation_id: payload["conversation_id"] = self.conversation_id if context: payload["context"] = context response = requests.post(url, json=payload, headers=headers) response.raise_for_status() result = response.json() self.conversation_id = result.get("conversation_id") return result def explain_code(self, project_id: int, file_path: str, code: str) -> str: """Explain code snippet""" context = { "files": [file_path], "code_snippet": code } response = self.send_message( message=f"Explain this code:\n{code}", project_id=project_id, context=context ) return response["response"] def generate_tests( self, project_id: int, file_path: str, code: str, framework: str = "pytest" ) -> str: """Generate tests for code""" response = self.send_message( message=f"/tests using {framework}", project_id=project_id, context={ "files": [file_path], "code_snippet": code } ) return response["response"] # Usage chat = GitLabDuoChat(token=os.getenv("GITLAB_TOKEN")) # Explain code explanation = chat.explain_code( project_id=12345, file_path="src/auth.py", code="def verify_token(token): ..." ) # Generate tests tests = chat.generate_tests( project_id=12345, file_path="src/auth.py", code="def verify_token(token): ...", framework="pytest" )
Security API
Explain Vulnerability
Endpoint: POST /api/v4/projects/:id/vulnerabilities/:vuln_id/explain
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/12345/vulnerabilities/789/explain"
Response:
{ "explanation": { "summary": "SQL Injection vulnerability in user authentication", "details": "This vulnerability allows attackers to...", "severity_justification": "Critical because...", "exploitation": "Attacker can exploit by...", "remediation": "Fix by using parameterized queries..." }, "model": "claude-sonnet-4" }
Get Remediation Suggestions
Endpoint: POST /api/v4/projects/:id/vulnerabilities/:vuln_id/remediate
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "context": { "file_path": "src/auth.py", "code": "query = \"SELECT * FROM users WHERE username='\" + username + \"'\"" } }' \ "https://gitlab.com/api/v4/projects/12345/vulnerabilities/789/remediate"
Response:
{ "suggestions": [ { "type": "code_change", "file": "src/auth.py", "original": "query = \"SELECT * FROM users WHERE username='\" + username + \"'\"", "suggested": "query = \"SELECT * FROM users WHERE username=%s\"\nparams = (username,)", "explanation": "Use parameterized queries to prevent SQL injection" } ], "difficulty": "easy", "breaking_changes": false }
SAST False Positive Analysis
Endpoint: POST /api/v4/projects/:id/security/sast/analyze
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "finding_id": "sast_456", "file_path": "src/admin.py", "code_context": "..." }' \ "https://gitlab.com/api/v4/projects/12345/security/sast/analyze"
Response:
{ "is_false_positive": true, "confidence": 0.90, "reasoning": "Input is validated against whitelist before use", "recommendation": "Mark as false positive" }
CI/CD AI API
Root Cause Analysis
Endpoint: POST /api/v4/projects/:id/pipelines/:pipeline_id/root_cause
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/12345/pipelines/67890/root_cause"
Response:
{ "root_cause": { "summary": "Missing dependency: pytest>=7.0", "details": "Test job failed because pytest 6.2 is installed...", "affected_job": "test:unit", "suggested_fix": { "file": "requirements.txt", "change": "pytest==6.2.0 -> pytest>=7.0.0" } }, "confidence": 0.95, "related_failures": [ {"pipeline_id": 67880, "job_name": "test:unit"} ] }
Python Client:
def analyze_pipeline_failure(project_id: int, pipeline_id: int) -> dict: """Get root cause analysis for failed pipeline""" url = f"https://gitlab.com/api/v4/projects/{project_id}/pipelines/{pipeline_id}/root_cause" headers = {"PRIVATE-TOKEN": os.getenv("GITLAB_TOKEN")} response = requests.post(url, headers=headers) response.raise_for_status() return response.json() # Usage analysis = analyze_pipeline_failure(project_id=12345, pipeline_id=67890) print(f"Root Cause: {analysis['root_cause']['summary']}") print(f"Fix: {analysis['root_cause']['suggested_fix']}")
Issue and MR API
Generate Issue Description
Endpoint: POST /api/v4/projects/:id/issues/:issue_iid/generate_description
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "title": "Add user export feature", "context": "Users need to export their data for GDPR compliance" }' \ "https://gitlab.com/api/v4/projects/12345/issues/789/generate_description"
Response:
{ "description": "## Summary\nImplement a feature allowing users to export...", "estimated_effort": "3-5 days", "suggested_labels": ["feature", "gdpr", "user-data"] }
Generate MR Summary
Endpoint: POST /api/v4/projects/:id/merge_requests/:mr_iid/generate_summary
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/12345/merge_requests/456/generate_summary"
Response:
{ "summary": { "title": "Suggested: Implement JWT authentication", "description": "## Changes\nThis MR implements...", "breaking_changes": true, "migration_required": true }, "metadata": { "files_changed": 8, "lines_added": 234, "lines_removed": 45, "complexity": "high" } }
Agent Platform API
Execute Flow
Endpoint: POST /api/v4/projects/:id/duo/flows/:flow_name/execute
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "parameters": { "issue_iid": 123 } }' \ "https://gitlab.com/api/v4/projects/12345/duo/flows/software_development/execute"
Response:
{ "execution_id": "exec_abc123", "status": "running", "started_at": "2026-01-08T14:23:00Z", "steps": [ { "name": "analyze_requirements", "status": "completed", "agent": "planner" }, { "name": "implement_feature", "status": "running", "agent": "software_developer" } ] }
Get Flow Status
Endpoint: GET /api/v4/projects/:id/duo/flows/executions/:execution_id
Request:
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/12345/duo/flows/executions/exec_abc123"
Response:
{ "execution_id": "exec_abc123", "status": "completed", "started_at": "2026-01-08T14:23:00Z", "completed_at": "2026-01-08T14:35:00Z", "duration_seconds": 720, "steps": [ { "name": "analyze_requirements", "status": "completed", "outputs": { "implementation_plan": "..." } }, { "name": "implement_feature", "status": "completed", "outputs": { "code_changes": "..." } } ], "artifacts": { "merge_request_iid": 789 } }
Create Custom Agent
Endpoint: POST /api/v4/groups/:id/duo/agents
Request:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "name": "code_quality_checker", "description": "Reviews code for quality and maintainability", "system_prompt": "You are a code quality expert...", "model": "claude-sonnet-4", "tools": ["read_file", "add_comment"] }' \ "https://gitlab.com/api/v4/groups/5678/duo/agents"
Response:
{ "id": 123, "name": "code_quality_checker", "description": "Reviews code for quality and maintainability", "created_at": "2026-01-08T14:23:00Z", "status": "active" }
Model Selection API
Get Available Models
Endpoint: GET /api/v4/duo/models
Request:
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/duo/models"
Response:
{ "models": [ { "id": "claude-sonnet-4", "provider": "anthropic", "capabilities": ["code_suggestions", "chat", "review"], "context_window": 200000, "cost_per_1k_tokens": 0.003 }, { "id": "gpt-4", "provider": "openai", "capabilities": ["chat", "code_suggestions"], "context_window": 128000, "cost_per_1k_tokens": 0.03 } ] }
Set Group Model Preferences
Endpoint: PUT /api/v4/groups/:id/duo/model_preferences
Request:
curl --request PUT \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "code_suggestions": "claude-sonnet-4", "chat": "claude-sonnet-4", "security_analysis": "claude-sonnet-4" }' \ "https://gitlab.com/api/v4/groups/5678/duo/model_preferences"
Rate Limits
Default Limits
Code Suggestions: 60 requests/minute
Chat: 20 messages/minute
Flow Executions: 10 concurrent executions
Security Analysis: 100 requests/hour
Check Rate Limit
Response Headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1704724800
Handle Rate Limits
import time import requests def make_request_with_retry(url, headers, payload, max_retries=3): """Make API request with rate limit handling""" for attempt in range(max_retries): response = requests.post(url, json=payload, headers=headers) if response.status_code == 429: # Rate limited reset_time = int(response.headers.get('X-RateLimit-Reset', 0)) wait_time = max(reset_time - time.time(), 0) + 1 print(f"Rate limited. Waiting {wait_time}s...") time.sleep(wait_time) continue response.raise_for_status() return response.json() raise Exception("Max retries exceeded")
Webhooks
Flow Completion Webhook
Setup:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "url": "https://your-app.com/webhooks/duo", "events": ["flow_completed", "flow_failed"] }' \ "https://gitlab.com/api/v4/projects/12345/duo/webhooks"
Webhook Payload:
{ "event": "flow_completed", "project_id": 12345, "flow_name": "software_development", "execution_id": "exec_abc123", "status": "completed", "duration_seconds": 720, "artifacts": { "merge_request_iid": 789 }, "timestamp": "2026-01-08T14:35:00Z" }
Handler Example:
from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/webhooks/duo', methods=['POST']) def handle_duo_webhook(): """Handle GitLab Duo webhook""" payload = request.json event = payload.get('event') if event == 'flow_completed': handle_flow_completed(payload) elif event == 'flow_failed': handle_flow_failed(payload) return jsonify({"status": "ok"}) def handle_flow_completed(payload): """Handle completed flow""" print(f"Flow {payload['flow_name']} completed") print(f"MR created: {payload['artifacts']['merge_request_iid']}") def handle_flow_failed(payload): """Handle failed flow""" print(f"Flow {payload['flow_name']} failed") print(f"Error: {payload.get('error_message')}")
SDK Examples
Python SDK
from gitlab_duo import GitLabDuo # Initialize client duo = GitLabDuo( token=os.getenv('GITLAB_TOKEN'), project_id=12345 ) # Code suggestions suggestions = duo.code.suggest( file_path="src/auth.py", content="def login(username, password):\n # ", cursor_position=(2, 6) ) # Chat response = duo.chat.send("Explain JWT authentication") explanation = response.message # Security vulnerability = duo.security.get_vulnerability(789) explanation = duo.security.explain(vulnerability) remediation = duo.security.suggest_fix(vulnerability) # Flows execution = duo.flows.execute( "software_development", parameters={"issue_iid": 123} ) # Wait for completion execution.wait() print(f"MR created: {execution.artifacts['merge_request_iid']}")
Node.js SDK
const { GitLabDuo } = require('@gitlab/duo'); // Initialize client const duo = new GitLabDuo({ token: process.env.GITLAB_TOKEN, projectId: 12345 }); // Code suggestions const suggestions = await duo.code.suggest({ filePath: 'src/auth.js', content: 'function login(username, password) {\n // ', cursorPosition: { line: 2, character: 2 } }); // Chat const response = await duo.chat.send('Explain JWT authentication'); console.log(response.message); // Flows const execution = await duo.flows.execute('software_development', { issueIid: 123 }); await execution.waitForCompletion(); console.log(`MR created: ${execution.artifacts.mergeRequestIid}`);
Error Handling
Common Errors
from gitlab_duo import GitLabDuo, DuoError, RateLimitError, ModelError duo = GitLabDuo(token=token, project_id=12345) try: suggestions = duo.code.suggest(...) except RateLimitError as e: print(f"Rate limited. Retry after {e.retry_after}s") time.sleep(e.retry_after) except ModelError as e: print(f"Model error: {e.message}") # Try different model or fallback except DuoError as e: print(f"Duo error: {e}")
Error Response Format
{ "error": { "code": "rate_limit_exceeded", "message": "API rate limit exceeded", "retry_after": 45, "documentation_url": "https://docs.gitlab.com/api/duo/" } }
Best Practices
1. Cache Responses
from functools import lru_cache @lru_cache(maxsize=100) def get_code_explanation(code_hash: str): """Cache code explanations""" return duo.chat.send(f"Explain: {code_hash}")
2. Batch Requests
# Instead of individual requests for file in files: suggestions = duo.code.suggest(file) # Batch them suggestions = duo.code.suggest_batch(files)
3. Use Webhooks for Long Operations
# Don't poll # while not execution.is_complete(): # time.sleep(5) # Use webhooks instead webhook_url = "https://your-app.com/duo/callback" execution = duo.flows.execute( "software_development", webhook_url=webhook_url )
4. Handle Partial Failures
try: execution = duo.flows.execute("software_development") execution.wait() except FlowError as e: # Some steps succeeded partial_results = e.completed_steps # Use what's available
5. Monitor Usage
# Track token usage usage = duo.usage.get_current_month() print(f"Tokens used: {usage.total_tokens}") print(f"Cost: ${usage.total_cost}") # Set alerts duo.usage.set_alert(threshold=10000, email="team@company.com")
Resources
Next Steps
- Best Practices - Advanced API usage patterns
- Overview - GitLab Duo features overview
- Agents - Agent Platform deep dive