| name | gitlab |
| description | GitLab REST API via curl. Use this skill to manage projects, issues, merge requests, and pipelines in GitLab. |
| vm0_secrets | GITLAB_TOKEN |
| vm0_vars | GITLAB_HOST |
GitLab API
Use the GitLab REST API via direct curl calls to manage projects, issues, merge requests, and pipelines.
Official docs:
https://docs.gitlab.com/ee/api/
When to Use
Use this skill when you need to:
- Manage projects - list, create, get project details
- Handle issues - create, update, close, list issues
- Work with merge requests - create, list, merge MRs
- Check pipelines - list jobs, view pipeline status
- Manage users - search users, get user info
Prerequisites
- Go to GitLab → User Settings → Access Tokens
- Create a personal access token with
apiscope - Copy the generated token
export GITLAB_HOST="gitlab.com" # Or your self-hosted GitLab domain
export GITLAB_TOKEN="glpat-xxxxxxxxxxxx" # Personal access token with api scope
Rate Limits
GitLab.com has rate limits of ~2000 requests per minute for authenticated users. Self-hosted instances may vary.
Important: When using
$VARin a command that pipes to another command, wrap the command containing$VARinbash -c '...'. Due to a Claude Code bug, environment variables are silently cleared when pipes are used directly.bash -c 'curl -s "https://api.example.com" -H "Authorization: Bearer $API_KEY"' | jq .
How to Use
All examples below assume GITLAB_HOST and GITLAB_TOKEN are set.
Base URL: https://${GITLAB_HOST}/api/v4
Note: Project IDs can be numeric (e.g.,
123) or URL-encoded paths (e.g.,mygroup%2Fmyproject).
1. Get Current User
Verify your authentication:
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/user" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '{id, username, name, email, state}
2. List Projects
Get projects accessible to you:
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects?membership=true&per_page=20" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '.[] | {id, path_with_namespace, visibility, default_branch}
Filter options:
membership=true- Only projects you're a member ofowned=true- Only projects you ownsearch=keyword- Search by namevisibility=public|internal|private- Filter by visibility
3. Get Project Details
Get details for a specific project:
PROJECT_ID="123" # or URL-encoded path like "mygroup%2Fmyproject"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '{id, name, path_with_namespace, default_branch, visibility, web_url}
4. List Project Issues
Get issues for a project:
PROJECT_ID="123"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues?state=opened&per_page=20" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '.[] | {iid, title, state, author: .author.username, labels, web_url}
Filter options:
state=opened|closed|all- Filter by statelabels=bug,urgent- Filter by labelsassignee_id=123- Filter by assigneesearch=keyword- Search in title/description
5. Get Issue Details
Get a specific issue:
PROJECT_ID="123"
ISSUE_IID="1" # Issue internal ID (not global ID)
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues/${ISSUE_IID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '{iid, title, description, state, author: .author.username, assignees: [.assignees[].username], labels, created_at, web_url}
6. Create Issue
Create a new issue in a project:
PROJECT_ID="123"
Write to /tmp/gitlab_request.json:
{
"title": "Bug: Login page not loading",
"description": "The login page shows a blank screen on mobile devices.",
"labels": "bug,frontend"
}
Then run:
bash -c 'curl -s -X POST "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{iid, title, web_url}'
7. Create Issue with Assignee and Milestone
Create issue with additional fields:
PROJECT_ID="123"
ASSIGNEE_ID="456"
MILESTONE_ID="1"
Write to /tmp/gitlab_request.json:
{
"title": "Implement user profile page",
"description": "Create a user profile page with avatar and bio.",
"assignee_ids": [${ASSIGNEE_ID}],
"milestone_id": ${MILESTONE_ID},
"labels": "feature,frontend"
}
Then run:
bash -c 'curl -s -X POST "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{iid, title, web_url}'
8. Update Issue
Update an existing issue:
PROJECT_ID="123"
ISSUE_IID="1"
Write to /tmp/gitlab_request.json:
{
"title": "Updated: Bug fix for login page",
"labels": "bug,frontend,in-progress"
}
Then run:
bash -c 'curl -s -X PUT "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues/${ISSUE_IID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{iid, title, labels, updated_at}'
9. Close Issue
Close an issue:
PROJECT_ID="123"
ISSUE_IID="1"
Write to /tmp/gitlab_request.json:
{
"state_event": "close"
}
Then run:
bash -c 'curl -s -X PUT "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues/${ISSUE_IID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{iid, title, state}'
Use "state_event": "reopen" to reopen a closed issue.
10. Add Comment to Issue
Add a note/comment to an issue:
PROJECT_ID="123"
ISSUE_IID="1"
Write to /tmp/gitlab_request.json:
{
"body": "Investigating this issue. Will update soon."
}
Then run:
bash -c 'curl -s -X POST "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues/${ISSUE_IID}/notes" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{id, body, author: .author.username, created_at}'
11. List Merge Requests
Get merge requests for a project:
PROJECT_ID="123"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/merge_requests?state=opened&per_page=20" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '.[] | {iid, title, state, source_branch, target_branch, author: .author.username, web_url}
Filter options:
state=opened|closed|merged|all- Filter by statescope=created_by_me|assigned_to_me|all- Filter by involvementlabels=review-needed- Filter by labels
12. Get Merge Request Details
Get a specific merge request:
PROJECT_ID="123"
MR_IID="1"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/merge_requests/${MR_IID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '{iid, title, state, source_branch, target_branch, author: .author.username, merge_status, has_conflicts, web_url}
13. Create Merge Request
Create a new merge request:
PROJECT_ID="123"
Write to /tmp/gitlab_request.json:
{
"source_branch": "feature/user-profile",
"target_branch": "main",
"title": "Add user profile page",
"description": "This MR adds a new user profile page with avatar support."
}
Then run:
bash -c 'curl -s -X POST "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/merge_requests" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{iid, title, web_url}'
14. Merge a Merge Request
Merge an MR (if it's ready):
PROJECT_ID="123"
MR_IID="1"
Write to /tmp/gitlab_request.json:
{
"merge_when_pipeline_succeeds": true
}
Then run:
bash -c 'curl -s -X PUT "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/merge_requests/${MR_IID}/merge" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{iid, title, state, merged_by: .merged_by.username}'
Options:
merge_when_pipeline_succeeds=true- Auto-merge when pipeline passessquash=true- Squash commits before mergingshould_remove_source_branch=true- Delete source branch after merge
15. List Pipelines
Get pipelines for a project:
PROJECT_ID="123"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/pipelines?per_page=10" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '.[] | {id, status, ref, sha: .sha[0:8], created_at, web_url}
16. Get Pipeline Details
Get details of a specific pipeline:
PROJECT_ID="123"
PIPELINE_ID="456"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/pipelines/${PIPELINE_ID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '{id, status, ref, duration, finished_at, web_url}
17. List Pipeline Jobs
Get jobs in a pipeline:
PROJECT_ID="123"
PIPELINE_ID="456"
bash -c 'curl -s "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/pipelines/${PIPELINE_ID}/jobs" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"' | jq '.[] | {id, name, stage, status, duration}
18. Search Users
Search for users:
bash -c 'curl -s -G "https://${GITLAB_HOST}/api/v4/users" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --data-urlencode "search=john"' | jq '.[] | {id, username, name, state}
19. Create Project
Create a new project:
Write to /tmp/gitlab_request.json:
{
"name": "my-new-project",
"visibility": "private",
"initialize_with_readme": true
}
Then run:
bash -c 'curl -s -X POST "https://${GITLAB_HOST}/api/v4/projects" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" --header "Content-Type: application/json" -d @/tmp/gitlab_request.json' | jq '{id, path_with_namespace, web_url}'
20. Delete Issue
Delete an issue (requires admin or owner permissions):
PROJECT_ID="123"
ISSUE_IID="1"
curl -s -X DELETE "https://${GITLAB_HOST}/api/v4/projects/${PROJECT_ID}/issues/${ISSUE_IID}" --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" -w "\nHTTP Status: %{http_code}"
Returns 204 No Content on success.
Project ID Encoding
When using project paths instead of numeric IDs, URL-encode the path:
mygroup/myproject→mygroup%2Fmyprojectmygroup/subgroup/myproject→mygroup%2Fsubgroup%2Fmyproject
# Using numeric ID
PROJECT_ID="123"
# Using encoded path
PROJECT_ID="mygroup%2Fmyproject"
Guidelines
- Use IID for issues/MRs: Within a project, use
iid(internal ID like #1, #2) not the globalid - URL-encode project paths: If using paths instead of numeric IDs, encode slashes as
%2F - Handle pagination: Use
per_pageandpageparams for large result sets - Check merge status: Before merging, verify
merge_statusiscan_be_merged - Rate limiting: Implement backoff if you receive 429 responses
- Self-hosted GitLab: Set
GITLAB_HOSTto your instance domain (without https://)