Skip to main content

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