gitlab-skill 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3885 @@
1
+ # GitLab API & CLI - Guía Completa Operativa
2
+
3
+ > **Fecha de recopilación**: 18 de febrero de 2026
4
+ > **Fuentes**: Documentación oficial de GitLab Docs
5
+ > **Enfoque**: Ejemplos prácticos y operativos sin UI web
6
+
7
+ ---
8
+
9
+ ## Tabla de Contenidos
10
+
11
+ 1. [Resumen por URL](#resumen-por-url)
12
+ 2. [Patrones de Uso Recomendados](#patrones-de-uso-recomendados)
13
+ 3. [Ejemplos Concretos por Features (25+)](#ejemplos-concretos-por-features)
14
+ 4. [Troubleshooting y Códigos HTTP](#troubleshooting-y-códigos-http)
15
+ 5. [Estructura Modular Recomendada](#estructura-modular-recomendada)
16
+
17
+ ---
18
+
19
+ ## Resumen por URL
20
+
21
+ ### 1. GraphQL API (`/api/graphql/`)
22
+
23
+ **Puntos clave accionables:**
24
+
25
+ - **Endpoint único**: `/api/graphql` - API versionless (sin versiones)
26
+ - **Autenticación**: OAuth 2.0, Personal/Project/Group access tokens, Session cookie
27
+ - **Headers de autenticación**: `Authorization: Bearer <token>` (recomendado) o `private_token` parameter
28
+ - **Scopes requeridos**: `api` (read-write) o `read_api` (read-only)
29
+ - **Límites**:
30
+ - Complejidad máxima: 200 (no autenticado), 250 (autenticado)
31
+ - Tamaño máximo de query: 10,000 caracteres
32
+ - Paginación: max 100 registros por página
33
+ - Blobs: límite de 20 MB para múltiples blobs
34
+ - Timeout: 30 segundos
35
+ - **Identificadores**: Global IDs (`gid://gitlab/Issue/123`), full paths, IIDs
36
+ - **Multiplex**: Soporta batching de múltiples queries en una sola request
37
+ - **Introspección**: Schemas estáticos disponibles en producción
38
+ - **Breaking changes**: Deprecación de 6 releases antes de remover
39
+ - **Testing futuro**: `?remove_deprecated=true` para verificar queries contra schema futuro
40
+
41
+ **Ventajas operativas:**
42
+ - Solicitar exactamente los datos necesarios (reduce requests)
43
+ - Exploración interactiva con GraphiQL (`/-/graphql-explorer`)
44
+ - Sin necesidad de parseo manual (client-side libraries)
45
+ - Batching de queries para optimizar performance
46
+
47
+ ---
48
+
49
+ ### 2. GraphQL Reference (`/api/graphql/reference/`)
50
+
51
+ **Puntos clave accionables:**
52
+
53
+ - **Referencia completa**: Schema detallado con todos los types, fields, arguments
54
+ - **Query type**: Entry points para todas las queries disponibles
55
+ - **Mutation types**: Operaciones de creación, actualización, eliminación
56
+ - **Deprecation markers**: Campos marcados como deprecated con alternativas
57
+ - **Experiments**: Items experimentales pueden removerse sin aviso
58
+ - **Feature flags**: Campos detrás de feature flags deshabilitados pueden removerse sin proceso de deprecación
59
+
60
+ **Uso práctico:**
61
+ - Consultar antes de implementar queries/mutations
62
+ - Verificar tipos de datos y argumentos requeridos
63
+ - Identificar campos deprecated para migrar código
64
+
65
+ ---
66
+
67
+ ### 3. GitLab CLI - glab (`/cli/`)
68
+
69
+ **Puntos clave accionables:**
70
+
71
+ - **Instalación**: Disponible para Linux, macOS, Windows (ver README)
72
+ - **Autenticación**: `glab auth login` (OAuth o personal access token)
73
+ - **Multi-instancia**: Soporta múltiples instancias autenticadas simultáneamente
74
+ - **Auto-detección**: Detecta hostname desde remotes Git del directorio actual
75
+
76
+ **Variables de entorno clave:**
77
+ - `GITLAB_TOKEN`: Token de autenticación (evita prompts)
78
+ - `GITLAB_HOST` / `GL_HOST`: URL del servidor GitLab (default: gitlab.com)
79
+ - `GLAB_CONFIG_DIR`: Directorio de configuración personalizado
80
+ - `NO_PROMPT`: Deshabilitar prompts interactivos
81
+ - `DEBUG`: Habilitar logging detallado
82
+ - `GLAB_ENABLE_CI_AUTOLOGIN`: Auto-login en CI/CD jobs (experimental)
83
+
84
+ **Comandos principales:**
85
+ - `glab api`: Llamadas directas a REST API
86
+ - `glab mr`: Merge requests (create, list, view, checkout, approve, merge)
87
+ - `glab issue`: Issues (create, list, view, close, reopen)
88
+ - `glab ci`: Pipelines y jobs (view, trace, retry, cancel)
89
+ - `glab repo`: Repositorios (clone, fork, view)
90
+ - `glab release`: Releases (create, list, view, delete)
91
+ - `glab variable`: Variables CI/CD (list, create, update, delete)
92
+
93
+ **Ventajas operativas:**
94
+ - Integración nativa con Git workflow
95
+ - No requiere cambio de contexto (terminal)
96
+ - Scripting y automatización fácil
97
+ - GitLab Duo Chat desde CLI
98
+
99
+ ---
100
+
101
+ ### 4. REST API (`/api/rest/`)
102
+
103
+ **Puntos clave accionables:**
104
+
105
+ - **Endpoint base**: `https://gitlab.example.com/api/v4`
106
+ - **Versión**: v4 (semantic versioning, major version)
107
+ - **Formato**: JSON (algunos endpoints soportan plain text)
108
+ - **Autenticación**: Personal/Project/Group access tokens, OAuth 2.0, Job tokens, Session cookie
109
+ - **Headers recomendados**: `PRIVATE-TOKEN: <token>` o `Authorization: Bearer <token>`
110
+
111
+ **Paginación:**
112
+
113
+ **Offset-based (default):**
114
+ - Parámetros: `page` (default: 1), `per_page` (default: 20, max: 100)
115
+ - Headers de respuesta: `x-total`, `x-total-pages`, `x-page`, `x-per-page`, `x-next-page`, `x-prev-page`
116
+ - Link header con `rel="next"`, `rel="prev"`, `rel="first"`, `rel="last"`
117
+ - Límite: max offset configurable (performance)
118
+
119
+ **Keyset-based (más eficiente):**
120
+ - Parámetros: `pagination=keyset`, `order_by`, `sort`, `per_page`
121
+ - Independiente del tamaño de la colección
122
+ - Link header con cursor para siguiente página
123
+ - Headers: `X-NEXT-CURSOR`, `X-PREV-CURSOR`
124
+ - Recursos soportados: Projects, Users, Groups, Audit events, Jobs, Registry tags, etc.
125
+
126
+ **Encoding:**
127
+ - Namespaced paths: URL-encoded (`/` → `%2F`)
128
+ - Ejemplo: `diaspora/diaspora` → `diaspora%2Fdiaspora`
129
+ - File paths, branches, tags: URL-encoded
130
+ - Arrays: `param[]=value1&param[]=value2`
131
+ - Hashes: `param[key]=value`
132
+ - ISO 8601 dates: `+` → `%2B` (ej: `2017-10-17T23:11:13.000%2B05:30`)
133
+
134
+ **Rate limits:**
135
+ - GitLab.com: Ver límites específicos en documentación
136
+ - Self-managed: Configurables por instancia
137
+ - Headers de respuesta indican límites y uso actual
138
+
139
+ ---
140
+
141
+ ### 5. OpenAPI Interactive (`/api/openapi/openapi_interactive/`)
142
+
143
+ **Nota**: Esta URL no pudo ser accedida completamente, pero típicamente proporciona:
144
+ - Interfaz Swagger/OpenAPI para explorar REST API
145
+ - Testing interactivo de endpoints
146
+ - Documentación generada automáticamente
147
+ - Ejemplos de requests/responses
148
+
149
+ ---
150
+
151
+ ### 6. REST API Troubleshooting (`/api/rest/troubleshooting/`)
152
+
153
+ **Puntos clave accionables:**
154
+
155
+ **Códigos de estado HTTP:**
156
+
157
+ | Código | Significado | Acción |
158
+ |--------|-------------|--------|
159
+ | `200 OK` | Éxito en GET/PUT/PATCH/DELETE | Recurso retornado como JSON |
160
+ | `201 Created` | Éxito en POST | Recurso creado retornado como JSON |
161
+ | `202 Accepted` | Aceptado para procesamiento | Recurso será procesado |
162
+ | `204 No Content` | Éxito sin contenido | No hay payload en respuesta |
163
+ | `301 Moved Permanently` | Recurso movido | Usar URL en header `Location` |
164
+ | `304 Not Modified` | No modificado desde última request | Usar caché |
165
+ | `400 Bad Request` | Atributo requerido faltante | Verificar parámetros |
166
+ | `401 Unauthorized` | No autenticado | Proporcionar token válido |
167
+ | `403 Forbidden` | No autorizado | Verificar permisos |
168
+ | `404 Not Found` | Recurso no encontrado | Verificar ID/path |
169
+ | `405 Method Not Allowed` | Método no soportado | Usar método correcto |
170
+ | `409 Conflict` | Recurso conflictivo existe | Resolver conflicto |
171
+ | `412 Precondition Failed` | Precondición falló | Verificar headers condicionales |
172
+ | `422 Unprocessable` | Entidad no procesable | Verificar validación |
173
+ | `429 Too Many Requests` | Rate limit excedido | Esperar y reintentar |
174
+ | `500 Server Error` | Error interno del servidor | Reportar a administrador |
175
+ | `503 Service Unavailable` | Servidor sobrecargado | Reintentar más tarde |
176
+
177
+ **Error 400 - Validación:**
178
+
179
+ Formato de error de validación:
180
+ ```json
181
+ {
182
+ "message": {
183
+ "<property-name>": [
184
+ "<error-message>",
185
+ "<error-message>"
186
+ ],
187
+ "<embed-entity>": {
188
+ "<property-name>": [
189
+ "<error-message>"
190
+ ]
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ **Debugging:**
197
+ - Incluir headers: `curl --include` (muestra headers HTTP)
198
+ - Incluir exit code: `curl --fail` (muestra código de salida)
199
+ - Verbose: `curl --verbose` (debug completo)
200
+
201
+ **Spam detection:**
202
+
203
+ Sin CAPTCHA:
204
+ ```json
205
+ {"message":{"error":"Your snippet has been recognized as spam and has been discarded."}}
206
+ ```
207
+
208
+ Con CAPTCHA:
209
+ ```json
210
+ {
211
+ "needs_captcha_response": true,
212
+ "spam_log_id": 42,
213
+ "captcha_site_key": "REDACTED",
214
+ "message": {"error": "Your snippet has been recognized as spam..."}
215
+ }
216
+ ```
217
+
218
+ Resolución:
219
+ 1. Obtener CAPTCHA response con `captcha_site_key` (Google reCAPTCHA v2)
220
+ 2. Reenviar con headers: `X-GitLab-Captcha-Response`, `X-GitLab-Spam-Log-Id`
221
+
222
+ **Error 404 con reverse proxy:**
223
+ - Problema: Proxy decodifica caracteres URL-encoded antes de pasar a GitLab
224
+ - Solución Apache: Agregar `AllowEncodedSlashes NoDecode` y `nocanon` flag en `ProxyPass`
225
+ - Solución NGINX: Configurar `proxy_pass` sin decodificación automática
226
+
227
+ ---
228
+
229
+ ### 7. GraphQL Examples (`/api/graphql/examples/`)
230
+
231
+ **Puntos clave accionables:**
232
+
233
+ **Métodos de ejecución:**
234
+ 1. **GraphiQL** (más fácil): `https://gitlab.com/-/graphql-explorer` o `https://<instance>/-/graphql-explorer`
235
+ 2. **Command line**: `curl` con POST a `/api/graphql`
236
+ 3. **Rails console**: Para self-managed/dedicated
237
+
238
+ **Estructura básica de query:**
239
+ ```graphql
240
+ query {
241
+ project(fullPath: "grupo/proyecto") {
242
+ id
243
+ name
244
+ issues {
245
+ nodes {
246
+ title
247
+ description
248
+ }
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ **Estructura básica de mutation:**
255
+ ```graphql
256
+ mutation {
257
+ createNote(input: {
258
+ noteableId: "gid://gitlab/Issue/123",
259
+ body: "Comentario"
260
+ }) {
261
+ note {
262
+ id
263
+ body
264
+ }
265
+ errors
266
+ }
267
+ }
268
+ ```
269
+
270
+ **Patterns importantes:**
271
+ - Siempre incluir `errors` en mutations
272
+ - Usar `nodes { }` para colecciones (sintaxis corta)
273
+ - Usar `edges { node { } }` cuando necesitas cursor para paginación
274
+ - Paginación: `first`, `last`, `after`, `before`, `endCursor`, `hasNextPage`
275
+ - Sorting: Usar argumentos `sort` donde disponible (ej: `issues(sort: created_asc)`)
276
+ - Introspección: `queryComplexity { score limit }` para verificar complejidad
277
+
278
+ **Ejemplos documentados:**
279
+ - Audit reports
280
+ - Issue boards
281
+ - Query users
282
+ - Custom emoji
283
+ - Branch rules
284
+ - Work items
285
+
286
+ ---
287
+
288
+ ## Patrones de Uso Recomendados
289
+
290
+ ### Autenticación
291
+
292
+ **REST API:**
293
+
294
+ ```bash
295
+ # Método 1: Header (recomendado)
296
+ curl --header "PRIVATE-TOKEN: <token>" \
297
+ --url "https://gitlab.example.com/api/v4/projects"
298
+
299
+ # Método 2: Bearer token (OAuth-compliant)
300
+ curl --header "Authorization: Bearer <token>" \
301
+ --url "https://gitlab.example.com/api/v4/projects"
302
+
303
+ # Método 3: Query parameter (menos seguro)
304
+ curl --url "https://gitlab.example.com/api/v4/projects?private_token=<token>"
305
+
306
+ # Job token (CI/CD)
307
+ curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \
308
+ --url "https://gitlab.example.com/api/v4/projects/1/releases"
309
+ ```
310
+
311
+ **GraphQL API:**
312
+
313
+ ```bash
314
+ # Header (recomendado)
315
+ curl --request POST \
316
+ --header "Authorization: Bearer $GRAPHQL_TOKEN" \
317
+ --header "Content-Type: application/json" \
318
+ --data '{"query": "query {currentUser {name}}"}' \
319
+ --url "https://gitlab.com/api/graphql"
320
+
321
+ # Query parameter
322
+ curl --request POST \
323
+ --header "Content-Type: application/json" \
324
+ --data '{"query": "query {currentUser {name}}"}' \
325
+ --url "https://gitlab.com/api/graphql?private_token=<token>"
326
+ ```
327
+
328
+ **glab CLI:**
329
+
330
+ ```bash
331
+ # Autenticación interactiva
332
+ glab auth login
333
+
334
+ # Variable de entorno
335
+ export GITLAB_TOKEN="<token>"
336
+ glab api projects
337
+
338
+ # Archivo de configuración
339
+ glab config set token <token>
340
+ ```
341
+
342
+ ---
343
+
344
+ ### Paginación
345
+
346
+ **REST - Offset-based:**
347
+
348
+ ```bash
349
+ # Página 2, 50 items por página
350
+ curl --header "PRIVATE-TOKEN: <token>" \
351
+ --url "https://gitlab.example.com/api/v4/projects?page=2&per_page=50"
352
+
353
+ # Usar Link header para siguiente página
354
+ # Link: <https://gitlab.example.com/api/v4/projects?page=3&per_page=50>; rel="next"
355
+ ```
356
+
357
+ **REST - Keyset-based (más eficiente):**
358
+
359
+ ```bash
360
+ # Primera página
361
+ curl --header "PRIVATE-TOKEN: <token>" \
362
+ --url "https://gitlab.example.com/api/v4/projects?pagination=keyset&per_page=50&order_by=id&sort=asc"
363
+
364
+ # Siguiente página (usar cursor del Link header)
365
+ curl --header "PRIVATE-TOKEN: <token>" \
366
+ --url "https://gitlab.example.com/api/v4/projects?pagination=keyset&per_page=50&order_by=id&sort=asc&id_after=42"
367
+ ```
368
+
369
+ **GraphQL:**
370
+
371
+ ```graphql
372
+ # Primera página
373
+ query {
374
+ project(fullPath: "grupo/proyecto") {
375
+ issues(first: 10) {
376
+ edges {
377
+ node {
378
+ title
379
+ }
380
+ cursor
381
+ }
382
+ pageInfo {
383
+ endCursor
384
+ hasNextPage
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ # Siguiente página (usar endCursor)
391
+ query {
392
+ project(fullPath: "grupo/proyecto") {
393
+ issues(first: 10, after: "eyJpZCI6IjI3MDM4OTMzIn0") {
394
+ edges {
395
+ node {
396
+ title
397
+ }
398
+ cursor
399
+ }
400
+ pageInfo {
401
+ endCursor
402
+ hasNextPage
403
+ }
404
+ }
405
+ }
406
+ }
407
+ ```
408
+
409
+ ---
410
+
411
+ ### Manejo de Errores
412
+
413
+ **REST API:**
414
+
415
+ ```bash
416
+ # Capturar código de estado
417
+ HTTP_CODE=$(curl --write-out '%{http_code}' --silent --output /dev/null \
418
+ --header "PRIVATE-TOKEN: <token>" \
419
+ --url "https://gitlab.example.com/api/v4/projects/123")
420
+
421
+ if [ "$HTTP_CODE" -eq 200 ]; then
422
+ echo "Success"
423
+ elif [ "$HTTP_CODE" -eq 404 ]; then
424
+ echo "Project not found"
425
+ elif [ "$HTTP_CODE" -eq 401 ]; then
426
+ echo "Unauthorized - check token"
427
+ elif [ "$HTTP_CODE" -eq 429 ]; then
428
+ echo "Rate limit exceeded - wait and retry"
429
+ fi
430
+
431
+ # Incluir headers para debugging
432
+ curl --include --header "PRIVATE-TOKEN: <token>" \
433
+ --url "https://gitlab.example.com/api/v4/projects/123"
434
+ ```
435
+
436
+ **GraphQL API:**
437
+
438
+ ```bash
439
+ # GraphQL siempre retorna 200, verificar errors en response
440
+ RESPONSE=$(curl --request POST \
441
+ --header "Authorization: Bearer $TOKEN" \
442
+ --header "Content-Type: application/json" \
443
+ --data '{"query": "query {project(fullPath: \"invalid\") {id}}"}' \
444
+ --url "https://gitlab.com/api/graphql")
445
+
446
+ # Verificar si hay errores
447
+ if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
448
+ echo "GraphQL errors found:"
449
+ echo "$RESPONSE" | jq '.errors'
450
+ fi
451
+ ```
452
+
453
+ **glab CLI:**
454
+
455
+ ```bash
456
+ # glab retorna códigos de salida estándar
457
+ if glab api projects/123 > /dev/null 2>&1; then
458
+ echo "Project exists"
459
+ else
460
+ echo "Project not found or error: $?"
461
+ fi
462
+
463
+ # Capturar output y errores
464
+ OUTPUT=$(glab api projects/123 2>&1)
465
+ if [ $? -ne 0 ]; then
466
+ echo "Error: $OUTPUT"
467
+ fi
468
+ ```
469
+
470
+ ---
471
+
472
+ ### Rate Limits
473
+
474
+ **Estrategias:**
475
+
476
+ ```bash
477
+ # 1. Verificar headers de rate limit
478
+ curl --include --header "PRIVATE-TOKEN: <token>" \
479
+ --url "https://gitlab.example.com/api/v4/projects" | grep -i "ratelimit"
480
+
481
+ # 2. Implementar retry con backoff exponencial
482
+ retry_with_backoff() {
483
+ local max_attempts=5
484
+ local timeout=1
485
+ local attempt=1
486
+
487
+ while [ $attempt -le $max_attempts ]; do
488
+ HTTP_CODE=$(curl --write-out '%{http_code}' --silent --output response.json \
489
+ --header "PRIVATE-TOKEN: <token>" \
490
+ "$1")
491
+
492
+ if [ "$HTTP_CODE" -eq 429 ]; then
493
+ echo "Rate limited, waiting ${timeout}s..."
494
+ sleep $timeout
495
+ timeout=$((timeout * 2))
496
+ attempt=$((attempt + 1))
497
+ elif [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
498
+ cat response.json
499
+ return 0
500
+ else
501
+ echo "Error: HTTP $HTTP_CODE"
502
+ return 1
503
+ fi
504
+ done
505
+
506
+ echo "Max retries exceeded"
507
+ return 1
508
+ }
509
+
510
+ # 3. Usar keyset pagination para grandes datasets
511
+ # (más eficiente que offset pagination)
512
+
513
+ # 4. Batch operations cuando sea posible
514
+ # GraphQL multiplex:
515
+ curl --request POST \
516
+ --header "Authorization: Bearer $TOKEN" \
517
+ --header "Content-Type: application/json" \
518
+ --data '[
519
+ {"query": "query {project(fullPath: \"grupo/proyecto1\") {id}}"},
520
+ {"query": "query {project(fullPath: \"grupo/proyecto2\") {id}}"}
521
+ ]' \
522
+ --url "https://gitlab.com/api/graphql"
523
+ ```
524
+
525
+ ---
526
+
527
+ ## Ejemplos Concretos por Features
528
+
529
+ ### 1. Projects
530
+
531
+ #### REST API
532
+
533
+ **Listar todos los proyectos:**
534
+ ```bash
535
+ curl --header "PRIVATE-TOKEN: <token>" \
536
+ --url "https://gitlab.example.com/api/v4/projects"
537
+ ```
538
+
539
+ **Obtener proyecto específico:**
540
+ ```bash
541
+ # Por ID
542
+ curl --header "PRIVATE-TOKEN: <token>" \
543
+ --url "https://gitlab.example.com/api/v4/projects/123"
544
+
545
+ # Por path (URL-encoded)
546
+ curl --header "PRIVATE-TOKEN: <token>" \
547
+ --url "https://gitlab.example.com/api/v4/projects/grupo%2Fproyecto"
548
+ ```
549
+
550
+ **Crear proyecto:**
551
+ ```bash
552
+ curl --request POST \
553
+ --header "PRIVATE-TOKEN: <token>" \
554
+ --header "Content-Type: application/json" \
555
+ --data '{
556
+ "name": "Mi Proyecto",
557
+ "path": "mi-proyecto",
558
+ "description": "Descripción del proyecto",
559
+ "visibility": "private",
560
+ "initialize_with_readme": true
561
+ }' \
562
+ --url "https://gitlab.example.com/api/v4/projects"
563
+ ```
564
+
565
+ **Actualizar configuración del proyecto:**
566
+ ```bash
567
+ # Deshabilitar forking
568
+ curl --request PUT \
569
+ --header "PRIVATE-TOKEN: <token>" \
570
+ --header "Content-Type: application/json" \
571
+ --data '{"forking_access_level": "disabled"}' \
572
+ --url "https://gitlab.example.com/api/v4/projects/123"
573
+
574
+ # Configurar CI/CD
575
+ curl --request PUT \
576
+ --header "PRIVATE-TOKEN: <token>" \
577
+ --header "Content-Type: application/json" \
578
+ --data '{
579
+ "builds_access_level": "enabled",
580
+ "auto_devops_enabled": true,
581
+ "auto_cancel_pending_pipelines": "enabled"
582
+ }' \
583
+ --url "https://gitlab.example.com/api/v4/projects/123"
584
+ ```
585
+
586
+ **Archivar/Desarchivar proyecto:**
587
+ ```bash
588
+ # Archivar
589
+ curl --request POST \
590
+ --header "PRIVATE-TOKEN: <token>" \
591
+ --url "https://gitlab.example.com/api/v4/projects/123/archive"
592
+
593
+ # Desarchivar
594
+ curl --request POST \
595
+ --header "PRIVATE-TOKEN: <token>" \
596
+ --url "https://gitlab.example.com/api/v4/projects/123/unarchive"
597
+ ```
598
+
599
+ #### GraphQL API
600
+
601
+ **Query proyecto con issues:**
602
+ ```graphql
603
+ query {
604
+ project(fullPath: "grupo/proyecto") {
605
+ id
606
+ name
607
+ description
608
+ visibility
609
+ webUrl
610
+ createdAt
611
+ issues(first: 10, state: OPENED) {
612
+ nodes {
613
+ iid
614
+ title
615
+ state
616
+ createdAt
617
+ }
618
+ pageInfo {
619
+ hasNextPage
620
+ endCursor
621
+ }
622
+ }
623
+ }
624
+ }
625
+ ```
626
+
627
+ **Mutation: Actualizar proyecto:**
628
+ ```graphql
629
+ mutation {
630
+ updateProject(input: {
631
+ projectPath: "grupo/proyecto",
632
+ description: "Nueva descripción"
633
+ }) {
634
+ project {
635
+ id
636
+ description
637
+ }
638
+ errors
639
+ }
640
+ }
641
+ ```
642
+
643
+ #### glab CLI
644
+
645
+ ```bash
646
+ # Listar proyectos
647
+ glab repo list
648
+
649
+ # Ver proyecto actual
650
+ glab repo view
651
+
652
+ # Clonar proyecto
653
+ glab repo clone grupo/proyecto
654
+
655
+ # Fork proyecto
656
+ glab repo fork grupo/proyecto
657
+
658
+ # Archivar proyecto
659
+ glab api --method POST projects/123/archive
660
+ ```
661
+
662
+ ---
663
+
664
+ ### 2. Groups
665
+
666
+ #### REST API
667
+
668
+ **Listar grupos:**
669
+ ```bash
670
+ # Todos los grupos
671
+ curl --header "PRIVATE-TOKEN: <token>" \
672
+ --url "https://gitlab.example.com/api/v4/groups"
673
+
674
+ # Grupos con proyectos
675
+ curl --header "PRIVATE-TOKEN: <token>" \
676
+ --url "https://gitlab.example.com/api/v4/groups?with_projects=true"
677
+ ```
678
+
679
+ **Obtener grupo específico:**
680
+ ```bash
681
+ curl --header "PRIVATE-TOKEN: <token>" \
682
+ --url "https://gitlab.example.com/api/v4/groups/grupo-id"
683
+ ```
684
+
685
+ **Crear grupo:**
686
+ ```bash
687
+ curl --request POST \
688
+ --header "PRIVATE-TOKEN: <token>" \
689
+ --header "Content-Type: application/json" \
690
+ --data '{
691
+ "name": "Mi Grupo",
692
+ "path": "mi-grupo",
693
+ "description": "Descripción del grupo",
694
+ "visibility": "private"
695
+ }' \
696
+ --url "https://gitlab.example.com/api/v4/groups"
697
+ ```
698
+
699
+ **Listar proyectos de un grupo:**
700
+ ```bash
701
+ curl --header "PRIVATE-TOKEN: <token>" \
702
+ --url "https://gitlab.example.com/api/v4/groups/grupo-id/projects"
703
+ ```
704
+
705
+ #### GraphQL API
706
+
707
+ **Query grupo con proyectos:**
708
+ ```graphql
709
+ query {
710
+ group(fullPath: "mi-grupo") {
711
+ id
712
+ name
713
+ fullPath
714
+ projects {
715
+ nodes {
716
+ id
717
+ name
718
+ visibility
719
+ }
720
+ }
721
+ }
722
+ }
723
+ ```
724
+
725
+ #### glab CLI
726
+
727
+ ```bash
728
+ # Listar grupos
729
+ glab api groups
730
+
731
+ # Ver grupo específico
732
+ glab api groups/grupo-id
733
+ ```
734
+
735
+ ---
736
+
737
+ ### 3. Issues
738
+
739
+ #### REST API
740
+
741
+ **Listar issues de un proyecto:**
742
+ ```bash
743
+ # Todas las issues
744
+ curl --header "PRIVATE-TOKEN: <token>" \
745
+ --url "https://gitlab.example.com/api/v4/projects/123/issues"
746
+
747
+ # Issues abiertas
748
+ curl --header "PRIVATE-TOKEN: <token>" \
749
+ --url "https://gitlab.example.com/api/v4/projects/123/issues?state=opened"
750
+
751
+ # Issues con filtros
752
+ curl --header "PRIVATE-TOKEN: <token>" \
753
+ --url "https://gitlab.example.com/api/v4/projects/123/issues?labels=bug,critical&assignee_id=5"
754
+ ```
755
+
756
+ **Obtener issue específica:**
757
+ ```bash
758
+ # Por IID (internal ID)
759
+ curl --header "PRIVATE-TOKEN: <token>" \
760
+ --url "https://gitlab.example.com/api/v4/projects/123/issues/42"
761
+ ```
762
+
763
+ **Crear issue:**
764
+ ```bash
765
+ curl --request POST \
766
+ --header "PRIVATE-TOKEN: <token>" \
767
+ --header "Content-Type: application/json" \
768
+ --data '{
769
+ "title": "Bug en login",
770
+ "description": "El login no funciona con usuarios nuevos",
771
+ "labels": "bug,high-priority",
772
+ "assignee_ids": [5, 10]
773
+ }' \
774
+ --url "https://gitlab.example.com/api/v4/projects/123/issues"
775
+ ```
776
+
777
+ **Actualizar issue:**
778
+ ```bash
779
+ curl --request PUT \
780
+ --header "PRIVATE-TOKEN: <token>" \
781
+ --header "Content-Type: application/json" \
782
+ --data '{
783
+ "state_event": "close",
784
+ "labels": "bug,resolved"
785
+ }' \
786
+ --url "https://gitlab.example.com/api/v4/projects/123/issues/42"
787
+ ```
788
+
789
+ **Agregar comentario a issue:**
790
+ ```bash
791
+ curl --request POST \
792
+ --header "PRIVATE-TOKEN: <token>" \
793
+ --header "Content-Type: application/json" \
794
+ --data '{
795
+ "body": "Este issue ha sido resuelto en el commit abc123"
796
+ }' \
797
+ --url "https://gitlab.example.com/api/v4/projects/123/issues/42/notes"
798
+ ```
799
+
800
+ #### GraphQL API
801
+
802
+ **Query issues con filtros:**
803
+ ```graphql
804
+ query {
805
+ project(fullPath: "grupo/proyecto") {
806
+ issues(
807
+ state: OPENED
808
+ labelName: ["bug", "critical"]
809
+ first: 20
810
+ ) {
811
+ nodes {
812
+ iid
813
+ title
814
+ description
815
+ state
816
+ labels {
817
+ nodes {
818
+ title
819
+ }
820
+ }
821
+ assignees {
822
+ nodes {
823
+ username
824
+ }
825
+ }
826
+ createdAt
827
+ updatedAt
828
+ }
829
+ pageInfo {
830
+ hasNextPage
831
+ endCursor
832
+ }
833
+ }
834
+ }
835
+ }
836
+ ```
837
+
838
+ **Mutation: Crear issue:**
839
+ ```graphql
840
+ mutation {
841
+ createIssue(input: {
842
+ projectPath: "grupo/proyecto",
843
+ title: "Bug en login",
844
+ description: "El login no funciona con usuarios nuevos",
845
+ labelIds: ["gid://gitlab/Label/1", "gid://gitlab/Label/2"]
846
+ }) {
847
+ issue {
848
+ iid
849
+ title
850
+ webUrl
851
+ }
852
+ errors
853
+ }
854
+ }
855
+ ```
856
+
857
+ **Mutation: Cerrar issue:**
858
+ ```graphql
859
+ mutation {
860
+ issueSetLocked(input: {
861
+ projectPath: "grupo/proyecto",
862
+ iid: "42",
863
+ locked: true
864
+ }) {
865
+ issue {
866
+ id
867
+ iid
868
+ }
869
+ errors
870
+ }
871
+ }
872
+
873
+ mutation {
874
+ updateIssue(input: {
875
+ projectPath: "grupo/proyecto",
876
+ iid: "42",
877
+ stateEvent: CLOSE
878
+ }) {
879
+ issue {
880
+ iid
881
+ state
882
+ }
883
+ errors
884
+ }
885
+ }
886
+ ```
887
+
888
+ **Mutation: Agregar comentario:**
889
+ ```graphql
890
+ mutation {
891
+ createNote(input: {
892
+ noteableId: "gid://gitlab/Issue/123456",
893
+ body: "Este issue ha sido resuelto en el commit abc123"
894
+ }) {
895
+ note {
896
+ id
897
+ body
898
+ createdAt
899
+ }
900
+ errors
901
+ }
902
+ }
903
+ ```
904
+
905
+ #### glab CLI
906
+
907
+ ```bash
908
+ # Listar issues
909
+ glab issue list
910
+
911
+ # Listar issues abiertas con filtro
912
+ glab issue list --state opened --label bug
913
+
914
+ # Ver issue específica
915
+ glab issue view 42
916
+
917
+ # Ver issue en browser
918
+ glab issue view 42 --web
919
+
920
+ # Crear issue
921
+ glab issue create --title "Bug en login" \
922
+ --description "El login no funciona" \
923
+ --label bug,high-priority
924
+
925
+ # Cerrar issue
926
+ glab issue close 42
927
+
928
+ # Reabrir issue
929
+ glab issue reopen 42
930
+
931
+ # Agregar comentario
932
+ glab issue note 42 --message "Resuelto en commit abc123"
933
+ ```
934
+
935
+ ---
936
+
937
+ ### 4. Merge Requests
938
+
939
+ #### REST API
940
+
941
+ **Listar merge requests:**
942
+ ```bash
943
+ # Todos los MRs del proyecto
944
+ curl --header "PRIVATE-TOKEN: <token>" \
945
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests"
946
+
947
+ # MRs abiertos
948
+ curl --header "PRIVATE-TOKEN: <token>" \
949
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests?state=opened"
950
+
951
+ # MRs con filtros
952
+ curl --header "PRIVATE-TOKEN: <token>" \
953
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests?state=opened&labels=feature&author_id=5"
954
+ ```
955
+
956
+ **Obtener MR específico:**
957
+ ```bash
958
+ curl --header "PRIVATE-TOKEN: <token>" \
959
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10"
960
+ ```
961
+
962
+ **Crear merge request:**
963
+ ```bash
964
+ curl --request POST \
965
+ --header "PRIVATE-TOKEN: <token>" \
966
+ --header "Content-Type: application/json" \
967
+ --data '{
968
+ "source_branch": "feature-branch",
969
+ "target_branch": "main",
970
+ "title": "Add new feature",
971
+ "description": "This MR adds a new feature",
972
+ "assignee_id": 5,
973
+ "labels": "feature,ready-for-review"
974
+ }' \
975
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests"
976
+ ```
977
+
978
+ **Actualizar merge request:**
979
+ ```bash
980
+ curl --request PUT \
981
+ --header "PRIVATE-TOKEN: <token>" \
982
+ --header "Content-Type: application/json" \
983
+ --data '{
984
+ "title": "Updated title",
985
+ "labels": "feature,reviewed"
986
+ }' \
987
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10"
988
+ ```
989
+
990
+ **Aprobar merge request:**
991
+ ```bash
992
+ curl --request POST \
993
+ --header "PRIVATE-TOKEN: <token>" \
994
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approve"
995
+ ```
996
+
997
+ **Merge merge request:**
998
+ ```bash
999
+ curl --request PUT \
1000
+ --header "PRIVATE-TOKEN: <token>" \
1001
+ --header "Content-Type: application/json" \
1002
+ --data '{
1003
+ "merge_commit_message": "Merge feature branch",
1004
+ "squash": true,
1005
+ "should_remove_source_branch": true
1006
+ }' \
1007
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/merge"
1008
+ ```
1009
+
1010
+ **Obtener cambios del MR:**
1011
+ ```bash
1012
+ curl --header "PRIVATE-TOKEN: <token>" \
1013
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/changes"
1014
+ ```
1015
+
1016
+ **Obtener commits del MR:**
1017
+ ```bash
1018
+ curl --header "PRIVATE-TOKEN: <token>" \
1019
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/commits"
1020
+ ```
1021
+
1022
+ #### GraphQL API
1023
+
1024
+ **Query merge requests:**
1025
+ ```graphql
1026
+ query {
1027
+ project(fullPath: "grupo/proyecto") {
1028
+ mergeRequests(
1029
+ state: OPENED
1030
+ first: 20
1031
+ ) {
1032
+ nodes {
1033
+ iid
1034
+ title
1035
+ description
1036
+ state
1037
+ sourceBranch
1038
+ targetBranch
1039
+ author {
1040
+ username
1041
+ }
1042
+ assignees {
1043
+ nodes {
1044
+ username
1045
+ }
1046
+ }
1047
+ labels {
1048
+ nodes {
1049
+ title
1050
+ }
1051
+ }
1052
+ createdAt
1053
+ updatedAt
1054
+ webUrl
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+ ```
1060
+
1061
+ **Mutation: Crear merge request:**
1062
+ ```graphql
1063
+ mutation {
1064
+ mergeRequestCreate(input: {
1065
+ projectPath: "grupo/proyecto",
1066
+ sourceBranch: "feature-branch",
1067
+ targetBranch: "main",
1068
+ title: "Add new feature",
1069
+ description: "This MR adds a new feature"
1070
+ }) {
1071
+ mergeRequest {
1072
+ iid
1073
+ title
1074
+ webUrl
1075
+ }
1076
+ errors
1077
+ }
1078
+ }
1079
+ ```
1080
+
1081
+ **Mutation: Actualizar merge request:**
1082
+ ```graphql
1083
+ mutation {
1084
+ mergeRequestUpdate(input: {
1085
+ projectPath: "grupo/proyecto",
1086
+ iid: "10",
1087
+ title: "Updated title"
1088
+ }) {
1089
+ mergeRequest {
1090
+ iid
1091
+ title
1092
+ }
1093
+ errors
1094
+ }
1095
+ }
1096
+ ```
1097
+
1098
+ **Mutation: Merge merge request:**
1099
+ ```graphql
1100
+ mutation {
1101
+ mergeRequestMerge(input: {
1102
+ projectPath: "grupo/proyecto",
1103
+ iid: "10",
1104
+ squash: true,
1105
+ shouldRemoveSourceBranch: true
1106
+ }) {
1107
+ mergeRequest {
1108
+ iid
1109
+ state
1110
+ }
1111
+ errors
1112
+ }
1113
+ }
1114
+ ```
1115
+
1116
+ #### glab CLI
1117
+
1118
+ ```bash
1119
+ # Listar merge requests
1120
+ glab mr list
1121
+
1122
+ # Listar MRs abiertos con filtro
1123
+ glab mr list --state opened --label feature
1124
+
1125
+ # Ver merge request
1126
+ glab mr view 10
1127
+
1128
+ # Ver MR en browser
1129
+ glab mr view 10 --web
1130
+
1131
+ # Crear merge request
1132
+ glab mr create --source-branch feature-branch \
1133
+ --target-branch main \
1134
+ --title "Add new feature" \
1135
+ --description "This MR adds a new feature" \
1136
+ --label feature
1137
+
1138
+ # Crear MR con template
1139
+ glab mr create --fill
1140
+
1141
+ # Checkout merge request localmente
1142
+ glab mr checkout 10
1143
+
1144
+ # Aprobar merge request
1145
+ glab mr approve 10
1146
+
1147
+ # Merge merge request
1148
+ glab mr merge 10
1149
+
1150
+ # Ver diff del MR
1151
+ glab mr diff 10
1152
+
1153
+ # Ver issues relacionados
1154
+ glab mr issues 10
1155
+
1156
+ # Agregar comentario
1157
+ glab mr note 10 --message "LGTM!"
1158
+ ```
1159
+
1160
+ ---
1161
+
1162
+ ### 5. Pipelines
1163
+
1164
+ #### REST API
1165
+
1166
+ **Listar pipelines:**
1167
+ ```bash
1168
+ # Todos los pipelines
1169
+ curl --header "PRIVATE-TOKEN: <token>" \
1170
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines"
1171
+
1172
+ # Pipelines con filtros
1173
+ curl --header "PRIVATE-TOKEN: <token>" \
1174
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines?status=running&ref=main"
1175
+
1176
+ # Pipelines por fecha
1177
+ curl --header "PRIVATE-TOKEN: <token>" \
1178
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines?updated_after=2026-01-01T00:00:00Z"
1179
+ ```
1180
+
1181
+ **Obtener pipeline específico:**
1182
+ ```bash
1183
+ curl --header "PRIVATE-TOKEN: <token>" \
1184
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456"
1185
+ ```
1186
+
1187
+ **Obtener último pipeline:**
1188
+ ```bash
1189
+ curl --header "PRIVATE-TOKEN: <token>" \
1190
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/latest?ref=main"
1191
+ ```
1192
+
1193
+ **Crear pipeline:**
1194
+ ```bash
1195
+ # Pipeline simple
1196
+ curl --request POST \
1197
+ --header "PRIVATE-TOKEN: <token>" \
1198
+ --url "https://gitlab.example.com/api/v4/projects/123/pipeline?ref=main"
1199
+
1200
+ # Pipeline con variables
1201
+ curl --request POST \
1202
+ --header "PRIVATE-TOKEN: <token>" \
1203
+ --header "Content-Type: application/json" \
1204
+ --data '{
1205
+ "ref": "main",
1206
+ "variables": [
1207
+ {"key": "DEPLOY_ENV", "value": "production"},
1208
+ {"key": "DEBUG", "value": "true"}
1209
+ ]
1210
+ }' \
1211
+ --url "https://gitlab.example.com/api/v4/projects/123/pipeline"
1212
+
1213
+ # Pipeline con inputs
1214
+ curl --request POST \
1215
+ --header "PRIVATE-TOKEN: <token>" \
1216
+ --header "Content-Type: application/json" \
1217
+ --data '{
1218
+ "ref": "main",
1219
+ "inputs": {
1220
+ "environment": "production",
1221
+ "scan_security": true,
1222
+ "level": 3
1223
+ }
1224
+ }' \
1225
+ --url "https://gitlab.example.com/api/v4/projects/123/pipeline"
1226
+ ```
1227
+
1228
+ **Reintentar pipeline:**
1229
+ ```bash
1230
+ curl --request POST \
1231
+ --header "PRIVATE-TOKEN: <token>" \
1232
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/retry"
1233
+ ```
1234
+
1235
+ **Cancelar pipeline:**
1236
+ ```bash
1237
+ curl --request POST \
1238
+ --header "PRIVATE-TOKEN: <token>" \
1239
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/cancel"
1240
+ ```
1241
+
1242
+ **Eliminar pipeline:**
1243
+ ```bash
1244
+ curl --request DELETE \
1245
+ --header "PRIVATE-TOKEN: <token>" \
1246
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456"
1247
+ ```
1248
+
1249
+ **Obtener variables del pipeline:**
1250
+ ```bash
1251
+ curl --header "PRIVATE-TOKEN: <token>" \
1252
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/variables"
1253
+ ```
1254
+
1255
+ **Obtener test report:**
1256
+ ```bash
1257
+ curl --header "PRIVATE-TOKEN: <token>" \
1258
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/test_report"
1259
+ ```
1260
+
1261
+ #### GraphQL API
1262
+
1263
+ **Query pipelines:**
1264
+ ```graphql
1265
+ query {
1266
+ project(fullPath: "grupo/proyecto") {
1267
+ pipelines(first: 20, status: RUNNING) {
1268
+ nodes {
1269
+ id
1270
+ iid
1271
+ status
1272
+ ref
1273
+ sha
1274
+ createdAt
1275
+ updatedAt
1276
+ duration
1277
+ coverage
1278
+ user {
1279
+ username
1280
+ }
1281
+ jobs {
1282
+ nodes {
1283
+ name
1284
+ status
1285
+ stage
1286
+ }
1287
+ }
1288
+ }
1289
+ }
1290
+ }
1291
+ }
1292
+ ```
1293
+
1294
+ **Mutation: Crear pipeline:**
1295
+ ```graphql
1296
+ mutation {
1297
+ pipelineCreate(input: {
1298
+ projectPath: "grupo/proyecto",
1299
+ ref: "main"
1300
+ }) {
1301
+ pipeline {
1302
+ id
1303
+ iid
1304
+ status
1305
+ }
1306
+ errors
1307
+ }
1308
+ }
1309
+ ```
1310
+
1311
+ **Mutation: Cancelar pipeline:**
1312
+ ```graphql
1313
+ mutation {
1314
+ pipelineCancel(input: {
1315
+ id: "gid://gitlab/Ci::Pipeline/456"
1316
+ }) {
1317
+ pipeline {
1318
+ id
1319
+ status
1320
+ }
1321
+ errors
1322
+ }
1323
+ }
1324
+ ```
1325
+
1326
+ **Mutation: Reintentar pipeline:**
1327
+ ```graphql
1328
+ mutation {
1329
+ pipelineRetry(input: {
1330
+ id: "gid://gitlab/Ci::Pipeline/456"
1331
+ }) {
1332
+ pipeline {
1333
+ id
1334
+ status
1335
+ }
1336
+ errors
1337
+ }
1338
+ }
1339
+ ```
1340
+
1341
+ #### glab CLI
1342
+
1343
+ ```bash
1344
+ # Listar pipelines
1345
+ glab ci list
1346
+ glab pipeline list
1347
+
1348
+ # Ver pipeline específico
1349
+ glab ci view 456
1350
+ glab pipeline view 456
1351
+
1352
+ # Ver status del pipeline actual
1353
+ glab ci status
1354
+
1355
+ # Ver trace de pipeline
1356
+ glab ci trace
1357
+
1358
+ # Reintentar pipeline
1359
+ glab pipeline retry 456
1360
+
1361
+ # Cancelar pipeline
1362
+ glab pipeline cancel 456
1363
+
1364
+ # Eliminar pipeline
1365
+ glab pipeline delete 456
1366
+
1367
+ # Ejecutar pipeline manualmente
1368
+ glab pipeline run
1369
+ glab pipeline run --branch main --variables "DEPLOY_ENV=production,DEBUG=true"
1370
+ ```
1371
+
1372
+ ---
1373
+
1374
+ ### 6. Jobs
1375
+
1376
+ #### REST API
1377
+
1378
+ **Listar jobs de un proyecto:**
1379
+ ```bash
1380
+ # Todos los jobs
1381
+ curl --header "PRIVATE-TOKEN: <token>" \
1382
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs"
1383
+
1384
+ # Jobs de un pipeline específico
1385
+ curl --header "PRIVATE-TOKEN: <token>" \
1386
+ --url "https://gitlab.example.com/api/v4/projects/123/pipelines/456/jobs"
1387
+
1388
+ # Jobs con filtros
1389
+ curl --header "PRIVATE-TOKEN: <token>" \
1390
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs?scope[]=failed&scope[]=running"
1391
+ ```
1392
+
1393
+ **Obtener job específico:**
1394
+ ```bash
1395
+ curl --header "PRIVATE-TOKEN: <token>" \
1396
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789"
1397
+ ```
1398
+
1399
+ **Obtener trace/log de un job:**
1400
+ ```bash
1401
+ curl --header "PRIVATE-TOKEN: <token>" \
1402
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789/trace"
1403
+ ```
1404
+
1405
+ **Reintentar job:**
1406
+ ```bash
1407
+ curl --request POST \
1408
+ --header "PRIVATE-TOKEN: <token>" \
1409
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789/retry"
1410
+ ```
1411
+
1412
+ **Cancelar job:**
1413
+ ```bash
1414
+ curl --request POST \
1415
+ --header "PRIVATE-TOKEN: <token>" \
1416
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789/cancel"
1417
+ ```
1418
+
1419
+ **Ejecutar job manual:**
1420
+ ```bash
1421
+ curl --request POST \
1422
+ --header "PRIVATE-TOKEN: <token>" \
1423
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789/play"
1424
+ ```
1425
+
1426
+ **Descargar artifacts:**
1427
+ ```bash
1428
+ # Artifacts de un job específico
1429
+ curl --header "PRIVATE-TOKEN: <token>" \
1430
+ --output artifacts.zip \
1431
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789/artifacts"
1432
+
1433
+ # Artifacts por job name y ref
1434
+ curl --header "PRIVATE-TOKEN: <token>" \
1435
+ --output artifacts.zip \
1436
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/artifacts/main/download?job=build"
1437
+ ```
1438
+
1439
+ **Obtener archivo específico de artifacts:**
1440
+ ```bash
1441
+ curl --header "PRIVATE-TOKEN: <token>" \
1442
+ --url "https://gitlab.example.com/api/v4/projects/123/jobs/789/artifacts/path/to/file.txt"
1443
+ ```
1444
+
1445
+ #### GraphQL API
1446
+
1447
+ **Query jobs:**
1448
+ ```graphql
1449
+ query {
1450
+ project(fullPath: "grupo/proyecto") {
1451
+ pipeline(iid: "456") {
1452
+ jobs {
1453
+ nodes {
1454
+ id
1455
+ name
1456
+ status
1457
+ stage
1458
+ duration
1459
+ createdAt
1460
+ finishedAt
1461
+ webPath
1462
+ }
1463
+ }
1464
+ }
1465
+ }
1466
+ }
1467
+ ```
1468
+
1469
+ #### glab CLI
1470
+
1471
+ ```bash
1472
+ # Listar jobs del pipeline actual
1473
+ glab ci view
1474
+
1475
+ # Ver trace de un job
1476
+ glab ci trace
1477
+ glab ci trace <job-id>
1478
+
1479
+ # Ver trace de job específico por nombre
1480
+ glab ci trace --job build
1481
+
1482
+ # Reintentar job
1483
+ glab job retry <job-id>
1484
+
1485
+ # Cancelar job
1486
+ glab job cancel <job-id>
1487
+
1488
+ # Ejecutar job manual
1489
+ glab job play <job-id>
1490
+
1491
+ # Descargar artifacts
1492
+ glab job artifacts <job-id>
1493
+ ```
1494
+
1495
+ ---
1496
+
1497
+ ### 7. Repository & Files
1498
+
1499
+ #### REST API - Repository
1500
+
1501
+ **Obtener información del repositorio:**
1502
+ ```bash
1503
+ curl --header "PRIVATE-TOKEN: <token>" \
1504
+ --url "https://gitlab.example.com/api/v4/projects/123/repository"
1505
+ ```
1506
+
1507
+ **Listar branches:**
1508
+ ```bash
1509
+ curl --header "PRIVATE-TOKEN: <token>" \
1510
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/branches"
1511
+ ```
1512
+
1513
+ **Crear branch:**
1514
+ ```bash
1515
+ curl --request POST \
1516
+ --header "PRIVATE-TOKEN: <token>" \
1517
+ --header "Content-Type: application/json" \
1518
+ --data '{
1519
+ "branch": "new-feature",
1520
+ "ref": "main"
1521
+ }' \
1522
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/branches"
1523
+ ```
1524
+
1525
+ **Eliminar branch:**
1526
+ ```bash
1527
+ curl --request DELETE \
1528
+ --header "PRIVATE-TOKEN: <token>" \
1529
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/branches/feature-branch"
1530
+ ```
1531
+
1532
+ **Listar tags:**
1533
+ ```bash
1534
+ curl --header "PRIVATE-TOKEN: <token>" \
1535
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/tags"
1536
+ ```
1537
+
1538
+ **Crear tag:**
1539
+ ```bash
1540
+ curl --request POST \
1541
+ --header "PRIVATE-TOKEN: <token>" \
1542
+ --header "Content-Type: application/json" \
1543
+ --data '{
1544
+ "tag_name": "v1.0.0",
1545
+ "ref": "main",
1546
+ "message": "Release version 1.0.0"
1547
+ }' \
1548
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/tags"
1549
+ ```
1550
+
1551
+ **Listar commits:**
1552
+ ```bash
1553
+ curl --header "PRIVATE-TOKEN: <token>" \
1554
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits"
1555
+
1556
+ # Con filtros
1557
+ curl --header "PRIVATE-TOKEN: <token>" \
1558
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits?ref_name=main&since=2026-01-01T00:00:00Z"
1559
+ ```
1560
+
1561
+ **Obtener commit específico:**
1562
+ ```bash
1563
+ curl --header "PRIVATE-TOKEN: <token>" \
1564
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123"
1565
+ ```
1566
+
1567
+ **Listar árbol de archivos:**
1568
+ ```bash
1569
+ curl --header "PRIVATE-TOKEN: <token>" \
1570
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/tree"
1571
+
1572
+ # Subdirectorio específico
1573
+ curl --header "PRIVATE-TOKEN: <token>" \
1574
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/tree?path=src/components"
1575
+ ```
1576
+
1577
+ #### REST API - Files
1578
+
1579
+ **Obtener archivo:**
1580
+ ```bash
1581
+ # Información completa (Base64 encoded)
1582
+ curl --header "PRIVATE-TOKEN: <token>" \
1583
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js?ref=main"
1584
+
1585
+ # Contenido raw
1586
+ curl --header "PRIVATE-TOKEN: <token>" \
1587
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js/raw?ref=main"
1588
+
1589
+ # Usar HEAD para default branch
1590
+ curl --header "PRIVATE-TOKEN: <token>" \
1591
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/README.md?ref=HEAD"
1592
+ ```
1593
+
1594
+ **Crear archivo:**
1595
+ ```bash
1596
+ curl --request POST \
1597
+ --header "PRIVATE-TOKEN: <token>" \
1598
+ --header "Content-Type: application/json" \
1599
+ --data '{
1600
+ "branch": "main",
1601
+ "commit_message": "Add new file",
1602
+ "content": "console.log(\"Hello World\");",
1603
+ "author_name": "John Doe",
1604
+ "author_email": "john@example.com"
1605
+ }' \
1606
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fnew-file.js"
1607
+ ```
1608
+
1609
+ **Actualizar archivo:**
1610
+ ```bash
1611
+ curl --request PUT \
1612
+ --header "PRIVATE-TOKEN: <token>" \
1613
+ --header "Content-Type: application/json" \
1614
+ --data '{
1615
+ "branch": "main",
1616
+ "commit_message": "Update file",
1617
+ "content": "console.log(\"Updated content\");"
1618
+ }' \
1619
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js"
1620
+ ```
1621
+
1622
+ **Eliminar archivo:**
1623
+ ```bash
1624
+ curl --request DELETE \
1625
+ --header "PRIVATE-TOKEN: <token>" \
1626
+ --header "Content-Type: application/json" \
1627
+ --data '{
1628
+ "branch": "main",
1629
+ "commit_message": "Delete file"
1630
+ }' \
1631
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fold-file.js"
1632
+ ```
1633
+
1634
+ **Obtener blame de archivo:**
1635
+ ```bash
1636
+ curl --header "PRIVATE-TOKEN: <token>" \
1637
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js/blame?ref=main"
1638
+
1639
+ # Rango específico
1640
+ curl --header "PRIVATE-TOKEN: <token>" \
1641
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/src%2Fapp.js/blame?ref=main&range[start]=1&range[end]=50"
1642
+ ```
1643
+
1644
+ #### GraphQL API
1645
+
1646
+ **Query repository:**
1647
+ ```graphql
1648
+ query {
1649
+ project(fullPath: "grupo/proyecto") {
1650
+ repository {
1651
+ rootRef
1652
+ tree {
1653
+ trees {
1654
+ nodes {
1655
+ name
1656
+ type
1657
+ path
1658
+ }
1659
+ }
1660
+ blobs {
1661
+ nodes {
1662
+ name
1663
+ path
1664
+ size
1665
+ }
1666
+ }
1667
+ }
1668
+ }
1669
+ }
1670
+ }
1671
+ ```
1672
+
1673
+ **Query archivo específico:**
1674
+ ```graphql
1675
+ query {
1676
+ project(fullPath: "grupo/proyecto") {
1677
+ repository {
1678
+ blobs(paths: ["README.md", "src/app.js"]) {
1679
+ nodes {
1680
+ path
1681
+ name
1682
+ size
1683
+ rawBlob
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ }
1689
+ ```
1690
+
1691
+ #### glab CLI
1692
+
1693
+ ```bash
1694
+ # Ver repositorio
1695
+ glab repo view
1696
+
1697
+ # Clonar repositorio
1698
+ glab repo clone grupo/proyecto
1699
+
1700
+ # Ver archivo
1701
+ glab repo view --file src/app.js
1702
+
1703
+ # Ver commits
1704
+ glab repo view --commits
1705
+
1706
+ # Ver branches
1707
+ glab repo view --branches
1708
+ ```
1709
+
1710
+ ---
1711
+
1712
+ ### 8. Releases
1713
+
1714
+ #### REST API
1715
+
1716
+ **Listar releases:**
1717
+ ```bash
1718
+ curl --header "PRIVATE-TOKEN: <token>" \
1719
+ --url "https://gitlab.example.com/api/v4/projects/123/releases"
1720
+ ```
1721
+
1722
+ **Obtener release específico:**
1723
+ ```bash
1724
+ curl --header "PRIVATE-TOKEN: <token>" \
1725
+ --url "https://gitlab.example.com/api/v4/projects/123/releases/v1.0.0"
1726
+ ```
1727
+
1728
+ **Crear release:**
1729
+ ```bash
1730
+ curl --request POST \
1731
+ --header "PRIVATE-TOKEN: <token>" \
1732
+ --header "Content-Type: application/json" \
1733
+ --data '{
1734
+ "tag_name": "v1.0.0",
1735
+ "name": "Release 1.0.0",
1736
+ "description": "## Features\n- New feature 1\n- New feature 2\n\n## Bug Fixes\n- Fixed bug 1",
1737
+ "ref": "main",
1738
+ "assets": {
1739
+ "links": [
1740
+ {
1741
+ "name": "Documentation",
1742
+ "url": "https://docs.example.com"
1743
+ }
1744
+ ]
1745
+ }
1746
+ }' \
1747
+ --url "https://gitlab.example.com/api/v4/projects/123/releases"
1748
+ ```
1749
+
1750
+ **Actualizar release:**
1751
+ ```bash
1752
+ curl --request PUT \
1753
+ --header "PRIVATE-TOKEN: <token>" \
1754
+ --header "Content-Type: application/json" \
1755
+ --data '{
1756
+ "name": "Release 1.0.0 - Updated",
1757
+ "description": "Updated description"
1758
+ }' \
1759
+ --url "https://gitlab.example.com/api/v4/projects/123/releases/v1.0.0"
1760
+ ```
1761
+
1762
+ **Eliminar release:**
1763
+ ```bash
1764
+ curl --request DELETE \
1765
+ --header "PRIVATE-TOKEN: <token>" \
1766
+ --url "https://gitlab.example.com/api/v4/projects/123/releases/v1.0.0"
1767
+ ```
1768
+
1769
+ #### GraphQL API
1770
+
1771
+ **Query releases:**
1772
+ ```graphql
1773
+ query {
1774
+ project(fullPath: "grupo/proyecto") {
1775
+ releases(first: 10) {
1776
+ nodes {
1777
+ tagName
1778
+ name
1779
+ description
1780
+ createdAt
1781
+ releasedAt
1782
+ assets {
1783
+ links {
1784
+ nodes {
1785
+ name
1786
+ url
1787
+ }
1788
+ }
1789
+ }
1790
+ }
1791
+ }
1792
+ }
1793
+ }
1794
+ ```
1795
+
1796
+ **Mutation: Crear release:**
1797
+ ```graphql
1798
+ mutation {
1799
+ releaseCreate(input: {
1800
+ projectPath: "grupo/proyecto",
1801
+ tagName: "v1.0.0",
1802
+ name: "Release 1.0.0",
1803
+ description: "Release notes",
1804
+ ref: "main"
1805
+ }) {
1806
+ release {
1807
+ tagName
1808
+ name
1809
+ }
1810
+ errors
1811
+ }
1812
+ }
1813
+ ```
1814
+
1815
+ #### glab CLI
1816
+
1817
+ ```bash
1818
+ # Listar releases
1819
+ glab release list
1820
+
1821
+ # Ver release específico
1822
+ glab release view v1.0.0
1823
+
1824
+ # Crear release
1825
+ glab release create v1.0.0 \
1826
+ --name "Release 1.0.0" \
1827
+ --notes "Release notes"
1828
+
1829
+ # Crear release con assets
1830
+ glab release create v1.0.0 \
1831
+ --name "Release 1.0.0" \
1832
+ --notes "Release notes" \
1833
+ --asset-link '{"name":"Documentation","url":"https://docs.example.com"}'
1834
+
1835
+ # Eliminar release
1836
+ glab release delete v1.0.0
1837
+ ```
1838
+
1839
+ ---
1840
+
1841
+ ### 9. Webhooks (Project Hooks)
1842
+
1843
+ #### REST API
1844
+
1845
+ **Listar hooks:**
1846
+ ```bash
1847
+ curl --header "PRIVATE-TOKEN: <token>" \
1848
+ --url "https://gitlab.example.com/api/v4/projects/123/hooks"
1849
+ ```
1850
+
1851
+ **Obtener hook específico:**
1852
+ ```bash
1853
+ curl --header "PRIVATE-TOKEN: <token>" \
1854
+ --url "https://gitlab.example.com/api/v4/projects/123/hooks/456"
1855
+ ```
1856
+
1857
+ **Crear hook:**
1858
+ ```bash
1859
+ curl --request POST \
1860
+ --header "PRIVATE-TOKEN: <token>" \
1861
+ --header "Content-Type: application/json" \
1862
+ --data '{
1863
+ "url": "https://example.com/webhook",
1864
+ "push_events": true,
1865
+ "merge_requests_events": true,
1866
+ "issues_events": true,
1867
+ "pipeline_events": true,
1868
+ "job_events": true,
1869
+ "token": "secret-token",
1870
+ "enable_ssl_verification": true
1871
+ }' \
1872
+ --url "https://gitlab.example.com/api/v4/projects/123/hooks"
1873
+ ```
1874
+
1875
+ **Actualizar hook:**
1876
+ ```bash
1877
+ curl --request PUT \
1878
+ --header "PRIVATE-TOKEN: <token>" \
1879
+ --header "Content-Type: application/json" \
1880
+ --data '{
1881
+ "url": "https://example.com/webhook-updated",
1882
+ "push_events": false
1883
+ }' \
1884
+ --url "https://gitlab.example.com/api/v4/projects/123/hooks/456"
1885
+ ```
1886
+
1887
+ **Eliminar hook:**
1888
+ ```bash
1889
+ curl --request DELETE \
1890
+ --header "PRIVATE-TOKEN: <token>" \
1891
+ --url "https://gitlab.example.com/api/v4/projects/123/hooks/456"
1892
+ ```
1893
+
1894
+ **Testear hook:**
1895
+ ```bash
1896
+ curl --request POST \
1897
+ --header "PRIVATE-TOKEN: <token>" \
1898
+ --url "https://gitlab.example.com/api/v4/projects/123/hooks/456/test/push_events"
1899
+ ```
1900
+
1901
+ #### glab CLI
1902
+
1903
+ ```bash
1904
+ # Listar hooks
1905
+ glab api projects/123/hooks
1906
+
1907
+ # Crear hook
1908
+ glab api --method POST projects/123/hooks \
1909
+ --field url=https://example.com/webhook \
1910
+ --field push_events=true \
1911
+ --field merge_requests_events=true
1912
+
1913
+ # Eliminar hook
1914
+ glab api --method DELETE projects/123/hooks/456
1915
+ ```
1916
+
1917
+ ---
1918
+
1919
+ ### 10. Variables CI/CD
1920
+
1921
+ #### REST API
1922
+
1923
+ **Listar variables:**
1924
+ ```bash
1925
+ # Variables de proyecto
1926
+ curl --header "PRIVATE-TOKEN: <token>" \
1927
+ --url "https://gitlab.example.com/api/v4/projects/123/variables"
1928
+
1929
+ # Variables de grupo
1930
+ curl --header "PRIVATE-TOKEN: <token>" \
1931
+ --url "https://gitlab.example.com/api/v4/groups/456/variables"
1932
+ ```
1933
+
1934
+ **Obtener variable específica:**
1935
+ ```bash
1936
+ curl --header "PRIVATE-TOKEN: <token>" \
1937
+ --url "https://gitlab.example.com/api/v4/projects/123/variables/DEPLOY_KEY"
1938
+ ```
1939
+
1940
+ **Crear variable:**
1941
+ ```bash
1942
+ curl --request POST \
1943
+ --header "PRIVATE-TOKEN: <token>" \
1944
+ --header "Content-Type: application/json" \
1945
+ --data '{
1946
+ "key": "DEPLOY_KEY",
1947
+ "value": "secret-value",
1948
+ "protected": true,
1949
+ "masked": true,
1950
+ "environment_scope": "production"
1951
+ }' \
1952
+ --url "https://gitlab.example.com/api/v4/projects/123/variables"
1953
+ ```
1954
+
1955
+ **Actualizar variable:**
1956
+ ```bash
1957
+ curl --request PUT \
1958
+ --header "PRIVATE-TOKEN: <token>" \
1959
+ --header "Content-Type: application/json" \
1960
+ --data '{
1961
+ "value": "new-secret-value",
1962
+ "protected": false
1963
+ }' \
1964
+ --url "https://gitlab.example.com/api/v4/projects/123/variables/DEPLOY_KEY"
1965
+ ```
1966
+
1967
+ **Eliminar variable:**
1968
+ ```bash
1969
+ curl --request DELETE \
1970
+ --header "PRIVATE-TOKEN: <token>" \
1971
+ --url "https://gitlab.example.com/api/v4/projects/123/variables/DEPLOY_KEY"
1972
+ ```
1973
+
1974
+ #### glab CLI
1975
+
1976
+ ```bash
1977
+ # Listar variables
1978
+ glab variable list
1979
+
1980
+ # Obtener variable específica
1981
+ glab variable get DEPLOY_KEY
1982
+
1983
+ # Crear variable
1984
+ glab variable set DEPLOY_KEY "secret-value" \
1985
+ --scope production \
1986
+ --protected \
1987
+ --masked
1988
+
1989
+ # Actualizar variable
1990
+ glab variable update DEPLOY_KEY "new-value"
1991
+
1992
+ # Eliminar variable
1993
+ glab variable delete DEPLOY_KEY
1994
+ ```
1995
+
1996
+ ---
1997
+
1998
+ ### 11. GraphQL - Queries Avanzadas
1999
+
2000
+ #### Query con múltiples recursos:
2001
+ ```graphql
2002
+ query {
2003
+ currentUser {
2004
+ id
2005
+ username
2006
+ name
2007
+ }
2008
+ project(fullPath: "grupo/proyecto") {
2009
+ id
2010
+ name
2011
+ issues(first: 5, state: OPENED) {
2012
+ nodes {
2013
+ iid
2014
+ title
2015
+ }
2016
+ }
2017
+ mergeRequests(first: 5, state: OPENED) {
2018
+ nodes {
2019
+ iid
2020
+ title
2021
+ }
2022
+ }
2023
+ }
2024
+ }
2025
+ ```
2026
+
2027
+ #### Query con paginación completa:
2028
+ ```graphql
2029
+ query($cursor: String) {
2030
+ project(fullPath: "grupo/proyecto") {
2031
+ issues(
2032
+ first: 20
2033
+ after: $cursor
2034
+ state: OPENED
2035
+ sort: CREATED_DESC
2036
+ ) {
2037
+ edges {
2038
+ node {
2039
+ iid
2040
+ title
2041
+ description
2042
+ labels {
2043
+ nodes {
2044
+ title
2045
+ color
2046
+ }
2047
+ }
2048
+ assignees {
2049
+ nodes {
2050
+ username
2051
+ name
2052
+ }
2053
+ }
2054
+ author {
2055
+ username
2056
+ }
2057
+ createdAt
2058
+ updatedAt
2059
+ }
2060
+ cursor
2061
+ }
2062
+ pageInfo {
2063
+ hasNextPage
2064
+ hasPreviousPage
2065
+ startCursor
2066
+ endCursor
2067
+ }
2068
+ }
2069
+ }
2070
+ }
2071
+ ```
2072
+
2073
+ Variables:
2074
+ ```json
2075
+ {
2076
+ "cursor": null
2077
+ }
2078
+ ```
2079
+
2080
+ #### Query con complejidad:
2081
+ ```graphql
2082
+ query {
2083
+ queryComplexity {
2084
+ score
2085
+ limit
2086
+ }
2087
+ project(fullPath: "grupo/proyecto") {
2088
+ name
2089
+ issues(first: 10) {
2090
+ nodes {
2091
+ title
2092
+ }
2093
+ }
2094
+ }
2095
+ }
2096
+ ```
2097
+
2098
+ #### Multiplex queries (batch):
2099
+ ```bash
2100
+ curl --request POST \
2101
+ --header "Authorization: Bearer $TOKEN" \
2102
+ --header "Content-Type: application/json" \
2103
+ --data '[
2104
+ {"query": "query {project(fullPath: \"grupo/proyecto1\") {id name}}"},
2105
+ {"query": "query {project(fullPath: \"grupo/proyecto2\") {id name}}"},
2106
+ {"query": "query {currentUser {username}}"}
2107
+ ]' \
2108
+ --url "https://gitlab.com/api/graphql"
2109
+ ```
2110
+
2111
+ ---
2112
+
2113
+ ### 12. GraphQL - Mutations Avanzadas
2114
+
2115
+ #### Mutation con múltiples operaciones:
2116
+ ```graphql
2117
+ mutation {
2118
+ createIssue: createIssue(input: {
2119
+ projectPath: "grupo/proyecto",
2120
+ title: "New issue"
2121
+ }) {
2122
+ issue {
2123
+ iid
2124
+ }
2125
+ errors
2126
+ }
2127
+
2128
+ updateProject: updateProject(input: {
2129
+ projectPath: "grupo/proyecto",
2130
+ description: "Updated description"
2131
+ }) {
2132
+ project {
2133
+ description
2134
+ }
2135
+ errors
2136
+ }
2137
+ }
2138
+ ```
2139
+
2140
+ #### Mutation con variables:
2141
+ ```graphql
2142
+ mutation CreateIssueWithLabels(
2143
+ $projectPath: ID!,
2144
+ $title: String!,
2145
+ $description: String,
2146
+ $labelIds: [LabelID!]
2147
+ ) {
2148
+ createIssue(input: {
2149
+ projectPath: $projectPath,
2150
+ title: $title,
2151
+ description: $description,
2152
+ labelIds: $labelIds
2153
+ }) {
2154
+ issue {
2155
+ iid
2156
+ title
2157
+ labels {
2158
+ nodes {
2159
+ title
2160
+ }
2161
+ }
2162
+ }
2163
+ errors
2164
+ }
2165
+ }
2166
+ ```
2167
+
2168
+ Variables:
2169
+ ```json
2170
+ {
2171
+ "projectPath": "grupo/proyecto",
2172
+ "title": "Bug en login",
2173
+ "description": "Descripción detallada",
2174
+ "labelIds": [
2175
+ "gid://gitlab/Label/1",
2176
+ "gid://gitlab/Label/2"
2177
+ ]
2178
+ }
2179
+ ```
2180
+
2181
+ ---
2182
+
2183
+ ### 13. glab API - Llamadas Directas
2184
+
2185
+ ```bash
2186
+ # GET request
2187
+ glab api projects/123
2188
+
2189
+ # GET con parámetros
2190
+ glab api "projects/123/issues?state=opened&labels=bug"
2191
+
2192
+ # POST request
2193
+ glab api --method POST projects/123/issues \
2194
+ --field title="New issue" \
2195
+ --field description="Description"
2196
+
2197
+ # PUT request
2198
+ glab api --method PUT projects/123/issues/42 \
2199
+ --field state_event=close
2200
+
2201
+ # DELETE request
2202
+ glab api --method DELETE projects/123/issues/42
2203
+
2204
+ # Con paginación
2205
+ glab api --paginate projects/123/issues
2206
+
2207
+ # Output en formato específico
2208
+ glab api projects/123 --jq '.name'
2209
+
2210
+ # Headers personalizados
2211
+ glab api --header "Custom-Header: value" projects/123
2212
+
2213
+ # Raw output
2214
+ glab api --raw projects/123
2215
+ ```
2216
+
2217
+ ---
2218
+
2219
+ ### 14. Búsqueda y Filtrado
2220
+
2221
+ #### REST API
2222
+
2223
+ **Buscar proyectos:**
2224
+ ```bash
2225
+ # Por nombre
2226
+ curl --header "PRIVATE-TOKEN: <token>" \
2227
+ --url "https://gitlab.example.com/api/v4/projects?search=my-project"
2228
+
2229
+ # Por visibilidad
2230
+ curl --header "PRIVATE-TOKEN: <token>" \
2231
+ --url "https://gitlab.example.com/api/v4/projects?visibility=public"
2232
+
2233
+ # Proyectos propios
2234
+ curl --header "PRIVATE-TOKEN: <token>" \
2235
+ --url "https://gitlab.example.com/api/v4/projects?owned=true"
2236
+ ```
2237
+
2238
+ **Buscar issues:**
2239
+ ```bash
2240
+ # Por título
2241
+ curl --header "PRIVATE-TOKEN: <token>" \
2242
+ --url "https://gitlab.example.com/api/v4/projects/123/issues?search=login"
2243
+
2244
+ # Por labels
2245
+ curl --header "PRIVATE-TOKEN: <token>" \
2246
+ --url "https://gitlab.example.com/api/v4/projects/123/issues?labels=bug,critical"
2247
+
2248
+ # Por assignee
2249
+ curl --header "PRIVATE-TOKEN: <token>" \
2250
+ --url "https://gitlab.example.com/api/v4/projects/123/issues?assignee_id=5"
2251
+
2252
+ # Por milestone
2253
+ curl --header "PRIVATE-TOKEN: <token>" \
2254
+ --url "https://gitlab.example.com/api/v4/projects/123/issues?milestone=v1.0"
2255
+ ```
2256
+
2257
+ **Buscar merge requests:**
2258
+ ```bash
2259
+ # Por autor
2260
+ curl --header "PRIVATE-TOKEN: <token>" \
2261
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests?author_id=5"
2262
+
2263
+ # Por reviewer
2264
+ curl --header "PRIVATE-TOKEN: <token>" \
2265
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests?reviewer_id=10"
2266
+
2267
+ # Por source branch
2268
+ curl --header "PRIVATE-TOKEN: <token>" \
2269
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests?source_branch=feature-branch"
2270
+ ```
2271
+
2272
+ #### GraphQL API
2273
+
2274
+ **Buscar con filtros:**
2275
+ ```graphql
2276
+ query {
2277
+ projects(
2278
+ search: "my-project"
2279
+ membership: true
2280
+ first: 20
2281
+ ) {
2282
+ nodes {
2283
+ id
2284
+ name
2285
+ fullPath
2286
+ }
2287
+ }
2288
+ }
2289
+ ```
2290
+
2291
+ **Buscar issues con filtros complejos:**
2292
+ ```graphql
2293
+ query {
2294
+ project(fullPath: "grupo/proyecto") {
2295
+ issues(
2296
+ search: "login"
2297
+ labelName: ["bug", "critical"]
2298
+ assigneeUsernames: ["user1", "user2"]
2299
+ state: OPENED
2300
+ sort: CREATED_DESC
2301
+ first: 20
2302
+ ) {
2303
+ nodes {
2304
+ iid
2305
+ title
2306
+ state
2307
+ }
2308
+ }
2309
+ }
2310
+ }
2311
+ ```
2312
+
2313
+ #### glab CLI
2314
+
2315
+ ```bash
2316
+ # Buscar issues
2317
+ glab issue list --search "login" --label bug
2318
+
2319
+ # Buscar merge requests
2320
+ glab mr list --search "feature" --author @me
2321
+
2322
+ # Buscar proyectos
2323
+ glab repo list --search "my-project"
2324
+ ```
2325
+
2326
+ ---
2327
+
2328
+ ### 15. Users y Autenticación
2329
+
2330
+ #### REST API
2331
+
2332
+ **Obtener usuario actual:**
2333
+ ```bash
2334
+ curl --header "PRIVATE-TOKEN: <token>" \
2335
+ --url "https://gitlab.example.com/api/v4/user"
2336
+ ```
2337
+
2338
+ **Listar usuarios:**
2339
+ ```bash
2340
+ curl --header "PRIVATE-TOKEN: <token>" \
2341
+ --url "https://gitlab.example.com/api/v4/users"
2342
+
2343
+ # Con búsqueda
2344
+ curl --header "PRIVATE-TOKEN: <token>" \
2345
+ --url "https://gitlab.example.com/api/v4/users?search=john"
2346
+ ```
2347
+
2348
+ **Obtener usuario específico:**
2349
+ ```bash
2350
+ curl --header "PRIVATE-TOKEN: <token>" \
2351
+ --url "https://gitlab.example.com/api/v4/users/123"
2352
+ ```
2353
+
2354
+ #### GraphQL API
2355
+
2356
+ **Query usuario actual:**
2357
+ ```graphql
2358
+ query {
2359
+ currentUser {
2360
+ id
2361
+ username
2362
+ name
2363
+ email
2364
+ publicEmail
2365
+ avatarUrl
2366
+ webUrl
2367
+ status {
2368
+ message
2369
+ emoji
2370
+ }
2371
+ }
2372
+ }
2373
+ ```
2374
+
2375
+ **Query usuarios:**
2376
+ ```graphql
2377
+ query {
2378
+ users(search: "john", first: 20) {
2379
+ nodes {
2380
+ id
2381
+ username
2382
+ name
2383
+ publicEmail
2384
+ }
2385
+ }
2386
+ }
2387
+ ```
2388
+
2389
+ #### glab CLI
2390
+
2391
+ ```bash
2392
+ # Ver usuario actual
2393
+ glab user view
2394
+
2395
+ # Ver usuario específico
2396
+ glab user view username
2397
+
2398
+ # Listar usuarios
2399
+ glab api users
2400
+ ```
2401
+
2402
+ ---
2403
+
2404
+ ### 16. Labels
2405
+
2406
+ #### REST API
2407
+
2408
+ **Listar labels:**
2409
+ ```bash
2410
+ curl --header "PRIVATE-TOKEN: <token>" \
2411
+ --url "https://gitlab.example.com/api/v4/projects/123/labels"
2412
+ ```
2413
+
2414
+ **Crear label:**
2415
+ ```bash
2416
+ curl --request POST \
2417
+ --header "PRIVATE-TOKEN: <token>" \
2418
+ --header "Content-Type: application/json" \
2419
+ --data '{
2420
+ "name": "bug",
2421
+ "color": "#FF0000",
2422
+ "description": "Bug tracking label"
2423
+ }' \
2424
+ --url "https://gitlab.example.com/api/v4/projects/123/labels"
2425
+ ```
2426
+
2427
+ **Actualizar label:**
2428
+ ```bash
2429
+ curl --request PUT \
2430
+ --header "PRIVATE-TOKEN: <token>" \
2431
+ --header "Content-Type: application/json" \
2432
+ --data '{
2433
+ "new_name": "critical-bug",
2434
+ "color": "#CC0000"
2435
+ }' \
2436
+ --url "https://gitlab.example.com/api/v4/projects/123/labels/bug"
2437
+ ```
2438
+
2439
+ **Eliminar label:**
2440
+ ```bash
2441
+ curl --request DELETE \
2442
+ --header "PRIVATE-TOKEN: <token>" \
2443
+ --url "https://gitlab.example.com/api/v4/projects/123/labels/bug"
2444
+ ```
2445
+
2446
+ #### glab CLI
2447
+
2448
+ ```bash
2449
+ # Listar labels
2450
+ glab label list
2451
+
2452
+ # Crear label
2453
+ glab label create bug --color "#FF0000" --description "Bug tracking"
2454
+
2455
+ # Eliminar label
2456
+ glab label delete bug
2457
+ ```
2458
+
2459
+ ---
2460
+
2461
+ ### 17. Milestones
2462
+
2463
+ #### REST API
2464
+
2465
+ **Listar milestones:**
2466
+ ```bash
2467
+ curl --header "PRIVATE-TOKEN: <token>" \
2468
+ --url "https://gitlab.example.com/api/v4/projects/123/milestones"
2469
+ ```
2470
+
2471
+ **Crear milestone:**
2472
+ ```bash
2473
+ curl --request POST \
2474
+ --header "PRIVATE-TOKEN: <token>" \
2475
+ --header "Content-Type: application/json" \
2476
+ --data '{
2477
+ "title": "v1.0",
2478
+ "description": "Release 1.0",
2479
+ "due_date": "2026-12-31",
2480
+ "start_date": "2026-01-01"
2481
+ }' \
2482
+ --url "https://gitlab.example.com/api/v4/projects/123/milestones"
2483
+ ```
2484
+
2485
+ **Actualizar milestone:**
2486
+ ```bash
2487
+ curl --request PUT \
2488
+ --header "PRIVATE-TOKEN: <token>" \
2489
+ --header "Content-Type: application/json" \
2490
+ --data '{
2491
+ "state_event": "close"
2492
+ }' \
2493
+ --url "https://gitlab.example.com/api/v4/projects/123/milestones/1"
2494
+ ```
2495
+
2496
+ #### glab CLI
2497
+
2498
+ ```bash
2499
+ # Listar milestones
2500
+ glab milestone list
2501
+
2502
+ # Ver milestone
2503
+ glab milestone view 1
2504
+
2505
+ # Crear milestone
2506
+ glab milestone create --title "v1.0" --due-date "2026-12-31"
2507
+
2508
+ # Cerrar milestone
2509
+ glab milestone close 1
2510
+ ```
2511
+
2512
+ ---
2513
+
2514
+ ### 18. Snippets
2515
+
2516
+ #### REST API
2517
+
2518
+ **Listar snippets:**
2519
+ ```bash
2520
+ # Snippets del usuario
2521
+ curl --header "PRIVATE-TOKEN: <token>" \
2522
+ --url "https://gitlab.example.com/api/v4/snippets"
2523
+
2524
+ # Snippets del proyecto
2525
+ curl --header "PRIVATE-TOKEN: <token>" \
2526
+ --url "https://gitlab.example.com/api/v4/projects/123/snippets"
2527
+ ```
2528
+
2529
+ **Crear snippet:**
2530
+ ```bash
2531
+ curl --request POST \
2532
+ --header "PRIVATE-TOKEN: <token>" \
2533
+ --header "Content-Type: application/json" \
2534
+ --data '{
2535
+ "title": "Example snippet",
2536
+ "file_name": "example.rb",
2537
+ "content": "puts \"Hello World\"",
2538
+ "visibility": "private"
2539
+ }' \
2540
+ --url "https://gitlab.example.com/api/v4/snippets"
2541
+ ```
2542
+
2543
+ **Obtener snippet:**
2544
+ ```bash
2545
+ curl --header "PRIVATE-TOKEN: <token>" \
2546
+ --url "https://gitlab.example.com/api/v4/snippets/456"
2547
+
2548
+ # Raw content
2549
+ curl --header "PRIVATE-TOKEN: <token>" \
2550
+ --url "https://gitlab.example.com/api/v4/snippets/456/raw"
2551
+ ```
2552
+
2553
+ #### glab CLI
2554
+
2555
+ ```bash
2556
+ # Listar snippets
2557
+ glab snippet list
2558
+
2559
+ # Ver snippet
2560
+ glab snippet view 456
2561
+
2562
+ # Crear snippet
2563
+ glab snippet create --title "Example" \
2564
+ --filename "example.rb" \
2565
+ --content "puts 'Hello'"
2566
+ ```
2567
+
2568
+ ---
2569
+
2570
+ ### 19. Runners
2571
+
2572
+ #### REST API
2573
+
2574
+ **Listar runners:**
2575
+ ```bash
2576
+ # Runners del proyecto
2577
+ curl --header "PRIVATE-TOKEN: <token>" \
2578
+ --url "https://gitlab.example.com/api/v4/projects/123/runners"
2579
+
2580
+ # Todos los runners (admin)
2581
+ curl --header "PRIVATE-TOKEN: <token>" \
2582
+ --url "https://gitlab.example.com/api/v4/runners/all"
2583
+ ```
2584
+
2585
+ **Obtener runner específico:**
2586
+ ```bash
2587
+ curl --header "PRIVATE-TOKEN: <token>" \
2588
+ --url "https://gitlab.example.com/api/v4/runners/456"
2589
+ ```
2590
+
2591
+ **Actualizar runner:**
2592
+ ```bash
2593
+ curl --request PUT \
2594
+ --header "PRIVATE-TOKEN: <token>" \
2595
+ --header "Content-Type: application/json" \
2596
+ --data '{
2597
+ "description": "Updated runner",
2598
+ "active": true,
2599
+ "tag_list": ["docker", "linux"]
2600
+ }' \
2601
+ --url "https://gitlab.example.com/api/v4/runners/456"
2602
+ ```
2603
+
2604
+ **Eliminar runner:**
2605
+ ```bash
2606
+ curl --request DELETE \
2607
+ --header "PRIVATE-TOKEN: <token>" \
2608
+ --url "https://gitlab.example.com/api/v4/runners/456"
2609
+ ```
2610
+
2611
+ #### GraphQL API
2612
+
2613
+ **Query runners:**
2614
+ ```graphql
2615
+ query {
2616
+ runner(id: "gid://gitlab/Ci::Runner/456") {
2617
+ id
2618
+ description
2619
+ active
2620
+ paused
2621
+ tagList
2622
+ runnerType
2623
+ }
2624
+ }
2625
+ ```
2626
+
2627
+ ---
2628
+
2629
+ ### 20. Container Registry
2630
+
2631
+ #### REST API
2632
+
2633
+ **Listar repositorios del registry:**
2634
+ ```bash
2635
+ curl --header "PRIVATE-TOKEN: <token>" \
2636
+ --url "https://gitlab.example.com/api/v4/projects/123/registry/repositories"
2637
+ ```
2638
+
2639
+ **Listar tags de un repositorio:**
2640
+ ```bash
2641
+ curl --header "PRIVATE-TOKEN: <token>" \
2642
+ --url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags"
2643
+ ```
2644
+
2645
+ **Obtener detalles de un tag:**
2646
+ ```bash
2647
+ curl --header "PRIVATE-TOKEN: <token>" \
2648
+ --url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags/latest"
2649
+ ```
2650
+
2651
+ **Eliminar tag:**
2652
+ ```bash
2653
+ curl --request DELETE \
2654
+ --header "PRIVATE-TOKEN: <token>" \
2655
+ --url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags/old-tag"
2656
+ ```
2657
+
2658
+ **Eliminar tags en bulk:**
2659
+ ```bash
2660
+ curl --request DELETE \
2661
+ --header "PRIVATE-TOKEN: <token>" \
2662
+ --header "Content-Type: application/json" \
2663
+ --data '{
2664
+ "name_regex_delete": ".*-dev",
2665
+ "keep_n": 5,
2666
+ "older_than": "1d"
2667
+ }' \
2668
+ --url "https://gitlab.example.com/api/v4/projects/123/registry/repositories/456/tags"
2669
+ ```
2670
+
2671
+ ---
2672
+
2673
+ ### 21. Deployments
2674
+
2675
+ #### REST API
2676
+
2677
+ **Listar deployments:**
2678
+ ```bash
2679
+ curl --header "PRIVATE-TOKEN: <token>" \
2680
+ --url "https://gitlab.example.com/api/v4/projects/123/deployments"
2681
+
2682
+ # Con filtros
2683
+ curl --header "PRIVATE-TOKEN: <token>" \
2684
+ --url "https://gitlab.example.com/api/v4/projects/123/deployments?environment=production&status=success"
2685
+ ```
2686
+
2687
+ **Obtener deployment específico:**
2688
+ ```bash
2689
+ curl --header "PRIVATE-TOKEN: <token>" \
2690
+ --url "https://gitlab.example.com/api/v4/projects/123/deployments/456"
2691
+ ```
2692
+
2693
+ **Crear deployment:**
2694
+ ```bash
2695
+ curl --request POST \
2696
+ --header "PRIVATE-TOKEN: <token>" \
2697
+ --header "Content-Type: application/json" \
2698
+ --data '{
2699
+ "environment": "production",
2700
+ "sha": "abc123",
2701
+ "ref": "main",
2702
+ "tag": false,
2703
+ "status": "success"
2704
+ }' \
2705
+ --url "https://gitlab.example.com/api/v4/projects/123/deployments"
2706
+ ```
2707
+
2708
+ #### GraphQL API
2709
+
2710
+ **Query deployments:**
2711
+ ```graphql
2712
+ query {
2713
+ project(fullPath: "grupo/proyecto") {
2714
+ deployments(first: 10) {
2715
+ nodes {
2716
+ id
2717
+ iid
2718
+ ref
2719
+ sha
2720
+ createdAt
2721
+ finishedAt
2722
+ status
2723
+ environment {
2724
+ name
2725
+ }
2726
+ }
2727
+ }
2728
+ }
2729
+ }
2730
+ ```
2731
+
2732
+ ---
2733
+
2734
+ ### 22. Environments
2735
+
2736
+ #### REST API
2737
+
2738
+ **Listar environments:**
2739
+ ```bash
2740
+ curl --header "PRIVATE-TOKEN: <token>" \
2741
+ --url "https://gitlab.example.com/api/v4/projects/123/environments"
2742
+ ```
2743
+
2744
+ **Crear environment:**
2745
+ ```bash
2746
+ curl --request POST \
2747
+ --header "PRIVATE-TOKEN: <token>" \
2748
+ --header "Content-Type: application/json" \
2749
+ --data '{
2750
+ "name": "staging",
2751
+ "external_url": "https://staging.example.com"
2752
+ }' \
2753
+ --url "https://gitlab.example.com/api/v4/projects/123/environments"
2754
+ ```
2755
+
2756
+ **Actualizar environment:**
2757
+ ```bash
2758
+ curl --request PUT \
2759
+ --header "PRIVATE-TOKEN: <token>" \
2760
+ --header "Content-Type: application/json" \
2761
+ --data '{
2762
+ "external_url": "https://new-staging.example.com"
2763
+ }' \
2764
+ --url "https://gitlab.example.com/api/v4/projects/123/environments/456"
2765
+ ```
2766
+
2767
+ **Stop environment:**
2768
+ ```bash
2769
+ curl --request POST \
2770
+ --header "PRIVATE-TOKEN: <token>" \
2771
+ --url "https://gitlab.example.com/api/v4/projects/123/environments/456/stop"
2772
+ ```
2773
+
2774
+ ---
2775
+
2776
+ ### 23. Protected Branches
2777
+
2778
+ #### REST API
2779
+
2780
+ **Listar protected branches:**
2781
+ ```bash
2782
+ curl --header "PRIVATE-TOKEN: <token>" \
2783
+ --url "https://gitlab.example.com/api/v4/projects/123/protected_branches"
2784
+ ```
2785
+
2786
+ **Proteger branch:**
2787
+ ```bash
2788
+ curl --request POST \
2789
+ --header "PRIVATE-TOKEN: <token>" \
2790
+ --header "Content-Type: application/json" \
2791
+ --data '{
2792
+ "name": "main",
2793
+ "push_access_level": 40,
2794
+ "merge_access_level": 30,
2795
+ "allow_force_push": false
2796
+ }' \
2797
+ --url "https://gitlab.example.com/api/v4/projects/123/protected_branches"
2798
+ ```
2799
+
2800
+ **Desproteger branch:**
2801
+ ```bash
2802
+ curl --request DELETE \
2803
+ --header "PRIVATE-TOKEN: <token>" \
2804
+ --url "https://gitlab.example.com/api/v4/projects/123/protected_branches/main"
2805
+ ```
2806
+
2807
+ ---
2808
+
2809
+ ### 24. Commits
2810
+
2811
+ #### REST API
2812
+
2813
+ **Crear commit con múltiples archivos:**
2814
+ ```bash
2815
+ curl --request POST \
2816
+ --header "PRIVATE-TOKEN: <token>" \
2817
+ --header "Content-Type: application/json" \
2818
+ --data '{
2819
+ "branch": "main",
2820
+ "commit_message": "Update multiple files",
2821
+ "actions": [
2822
+ {
2823
+ "action": "create",
2824
+ "file_path": "new-file.txt",
2825
+ "content": "New file content"
2826
+ },
2827
+ {
2828
+ "action": "update",
2829
+ "file_path": "existing-file.txt",
2830
+ "content": "Updated content"
2831
+ },
2832
+ {
2833
+ "action": "delete",
2834
+ "file_path": "old-file.txt"
2835
+ },
2836
+ {
2837
+ "action": "move",
2838
+ "file_path": "renamed-file.txt",
2839
+ "previous_path": "old-name.txt"
2840
+ }
2841
+ ]
2842
+ }' \
2843
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits"
2844
+ ```
2845
+
2846
+ **Obtener diff de commit:**
2847
+ ```bash
2848
+ curl --header "PRIVATE-TOKEN: <token>" \
2849
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123/diff"
2850
+ ```
2851
+
2852
+ **Obtener comentarios de commit:**
2853
+ ```bash
2854
+ curl --header "PRIVATE-TOKEN: <token>" \
2855
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123/comments"
2856
+ ```
2857
+
2858
+ **Agregar comentario a commit:**
2859
+ ```bash
2860
+ curl --request POST \
2861
+ --header "PRIVATE-TOKEN: <token>" \
2862
+ --header "Content-Type: application/json" \
2863
+ --data '{
2864
+ "note": "This commit looks good",
2865
+ "path": "src/app.js",
2866
+ "line": 10,
2867
+ "line_type": "new"
2868
+ }' \
2869
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/commits/abc123/comments"
2870
+ ```
2871
+
2872
+ ---
2873
+
2874
+ ### 25. Merge Request Approvals
2875
+
2876
+ #### REST API
2877
+
2878
+ **Obtener configuración de approvals:**
2879
+ ```bash
2880
+ curl --header "PRIVATE-TOKEN: <token>" \
2881
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approvals"
2882
+ ```
2883
+
2884
+ **Configurar approvals requeridos:**
2885
+ ```bash
2886
+ curl --request POST \
2887
+ --header "PRIVATE-TOKEN: <token>" \
2888
+ --header "Content-Type: application/json" \
2889
+ --data '{
2890
+ "approvals_required": 2
2891
+ }' \
2892
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approvals"
2893
+ ```
2894
+
2895
+ **Aprobar MR:**
2896
+ ```bash
2897
+ curl --request POST \
2898
+ --header "PRIVATE-TOKEN: <token>" \
2899
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/approve"
2900
+ ```
2901
+
2902
+ **Remover aprobación:**
2903
+ ```bash
2904
+ curl --request POST \
2905
+ --header "PRIVATE-TOKEN: <token>" \
2906
+ --url "https://gitlab.example.com/api/v4/projects/123/merge_requests/10/unapprove"
2907
+ ```
2908
+
2909
+ ---
2910
+
2911
+ ## Troubleshooting y Códigos HTTP
2912
+
2913
+ ### Códigos de Estado Comunes
2914
+
2915
+ #### 2xx - Éxito
2916
+
2917
+ **200 OK**
2918
+ - **Significado**: Request exitoso (GET/PUT/PATCH/DELETE)
2919
+ - **Acción**: Procesar respuesta JSON
2920
+ - **Ejemplo**:
2921
+ ```bash
2922
+ curl --header "PRIVATE-TOKEN: <token>" \
2923
+ --url "https://gitlab.example.com/api/v4/projects/123"
2924
+ # Response: 200 OK con datos del proyecto
2925
+ ```
2926
+
2927
+ **201 Created**
2928
+ - **Significado**: Recurso creado exitosamente (POST)
2929
+ - **Acción**: Recurso nuevo disponible en respuesta
2930
+ - **Ejemplo**:
2931
+ ```bash
2932
+ curl --request POST \
2933
+ --header "PRIVATE-TOKEN: <token>" \
2934
+ --header "Content-Type: application/json" \
2935
+ --data '{"title": "New issue"}' \
2936
+ --url "https://gitlab.example.com/api/v4/projects/123/issues"
2937
+ # Response: 201 Created con datos de la issue creada
2938
+ ```
2939
+
2940
+ **202 Accepted**
2941
+ - **Significado**: Request aceptado, procesamiento asíncrono
2942
+ - **Acción**: Verificar estado posteriormente
2943
+ - **Ejemplo**: Operaciones de importación, exports
2944
+
2945
+ **204 No Content**
2946
+ - **Significado**: Éxito sin contenido en respuesta
2947
+ - **Acción**: Operación completada, no hay datos adicionales
2948
+ - **Ejemplo**: DELETE exitoso
2949
+
2950
+ ---
2951
+
2952
+ #### 3xx - Redirección
2953
+
2954
+ **301 Moved Permanently**
2955
+ - **Significado**: Recurso movido permanentemente
2956
+ - **Acción**: Usar URL del header `Location`
2957
+ - **Ejemplo**: Proyecto renombrado o movido
2958
+ ```bash
2959
+ # Request a path antiguo
2960
+ curl --verbose --header "PRIVATE-TOKEN: <token>" \
2961
+ --url "https://gitlab.example.com/api/v4/projects/old-group%2Fold-project"
2962
+ # Response: 301 con Location: /api/v4/projects/new-group%2Fnew-project
2963
+ ```
2964
+
2965
+ **304 Not Modified**
2966
+ - **Significado**: Recurso no modificado desde última request
2967
+ - **Acción**: Usar caché local
2968
+ - **Ejemplo**: Request condicional con `If-Modified-Since`
2969
+
2970
+ ---
2971
+
2972
+ #### 4xx - Errores del Cliente
2973
+
2974
+ **400 Bad Request**
2975
+ - **Significado**: Parámetro requerido faltante o inválido
2976
+ - **Acción**: Verificar parámetros y formato
2977
+ - **Ejemplos**:
2978
+
2979
+ ```bash
2980
+ # Error: Atributo faltante
2981
+ curl --request POST \
2982
+ --header "PRIVATE-TOKEN: <token>" \
2983
+ --header "Content-Type: application/json" \
2984
+ --data '{"description": "Missing title"}' \
2985
+ --url "https://gitlab.example.com/api/v4/projects/123/issues"
2986
+ # Response: 400 {"message":"400 (Bad request) \"title\" not given"}
2987
+ ```
2988
+
2989
+ ```bash
2990
+ # Error: Validación fallida
2991
+ curl --request POST \
2992
+ --header "PRIVATE-TOKEN: <token>" \
2993
+ --header "Content-Type: application/json" \
2994
+ --data '{"title": "A", "description": "Too short title"}' \
2995
+ --url "https://gitlab.example.com/api/v4/projects/123/issues"
2996
+ # Response: 400 {"message":{"title":["is too short (minimum is 3 characters)"]}}
2997
+ ```
2998
+
2999
+ **Resolución**:
3000
+ 1. Verificar documentación del endpoint
3001
+ 2. Validar todos los campos requeridos
3002
+ 3. Verificar formato de datos (JSON válido)
3003
+ 4. Revisar tipos de datos (string, integer, boolean)
3004
+
3005
+ ---
3006
+
3007
+ **401 Unauthorized**
3008
+ - **Significado**: No autenticado o token inválido
3009
+ - **Acción**: Verificar token de autenticación
3010
+ - **Ejemplos**:
3011
+
3012
+ ```bash
3013
+ # Error: Token faltante
3014
+ curl --url "https://gitlab.example.com/api/v4/projects/123"
3015
+ # Response: 401 {"message":"401 Unauthorized"}
3016
+
3017
+ # Error: Token inválido
3018
+ curl --header "PRIVATE-TOKEN: invalid-token" \
3019
+ --url "https://gitlab.example.com/api/v4/projects/123"
3020
+ # Response: 401 {"message":"Invalid token"}
3021
+ ```
3022
+
3023
+ **Resolución**:
3024
+ 1. Verificar que el token existe y es válido
3025
+ 2. Verificar que el token no ha expirado
3026
+ 3. Verificar que el header está correctamente formateado
3027
+ 4. Para GraphQL: verificar scope del token (`api` o `read_api`)
3028
+
3029
+ ```bash
3030
+ # Verificar token
3031
+ curl --header "PRIVATE-TOKEN: <token>" \
3032
+ --url "https://gitlab.example.com/api/v4/user"
3033
+ # Si retorna datos del usuario, el token es válido
3034
+ ```
3035
+
3036
+ ---
3037
+
3038
+ **403 Forbidden**
3039
+ - **Significado**: Autenticado pero sin permisos
3040
+ - **Acción**: Verificar permisos del usuario/token
3041
+ - **Ejemplos**:
3042
+
3043
+ ```bash
3044
+ # Error: Sin permisos para eliminar proyecto
3045
+ curl --request DELETE \
3046
+ --header "PRIVATE-TOKEN: <token>" \
3047
+ --url "https://gitlab.example.com/api/v4/projects/123"
3048
+ # Response: 403 {"message":"403 Forbidden"}
3049
+
3050
+ # Error: Scope insuficiente
3051
+ curl --request POST \
3052
+ --header "PRIVATE-TOKEN: <read-only-token>" \
3053
+ --data '{"title": "New issue"}' \
3054
+ --url "https://gitlab.example.com/api/v4/projects/123/issues"
3055
+ # Response: 403 {"error":"insufficient_scope"}
3056
+ ```
3057
+
3058
+ **Resolución**:
3059
+ 1. Verificar rol del usuario (Guest, Reporter, Developer, Maintainer, Owner)
3060
+ 2. Verificar scope del token (`api` para write, `read_api` para read-only)
3061
+ 3. Verificar permisos del proyecto/grupo
3062
+ 4. Para operaciones de admin: verificar que el usuario es administrador
3063
+
3064
+ ---
3065
+
3066
+ **404 Not Found**
3067
+ - **Significado**: Recurso no encontrado o sin acceso
3068
+ - **Acción**: Verificar ID/path y permisos
3069
+ - **Ejemplos**:
3070
+
3071
+ ```bash
3072
+ # Error: Proyecto no existe
3073
+ curl --header "PRIVATE-TOKEN: <token>" \
3074
+ --url "https://gitlab.example.com/api/v4/projects/999999"
3075
+ # Response: 404 {"message":"404 Project Not Found"}
3076
+
3077
+ # Error: Path incorrecto (falta URL encoding)
3078
+ curl --header "PRIVATE-TOKEN: <token>" \
3079
+ --url "https://gitlab.example.com/api/v4/projects/grupo/proyecto"
3080
+ # Response: 404 (debe ser grupo%2Fproyecto)
3081
+
3082
+ # Error: Issue no existe
3083
+ curl --header "PRIVATE-TOKEN: <token>" \
3084
+ --url "https://gitlab.example.com/api/v4/projects/123/issues/999999"
3085
+ # Response: 404 {"message":"404 Not found"}
3086
+ ```
3087
+
3088
+ **Resolución**:
3089
+ 1. Verificar que el ID/path es correcto
3090
+ 2. URL-encode paths con `/` → `%2F`
3091
+ 3. Verificar que el recurso existe
3092
+ 4. Verificar permisos de acceso al recurso
3093
+ 5. Para proyectos: usar ID numérico en lugar de path si hay problemas
3094
+
3095
+ ```bash
3096
+ # Obtener ID de proyecto por path
3097
+ curl --header "PRIVATE-TOKEN: <token>" \
3098
+ --url "https://gitlab.example.com/api/v4/projects/grupo%2Fproyecto" | jq '.id'
3099
+ ```
3100
+
3101
+ ---
3102
+
3103
+ **409 Conflict**
3104
+ - **Significado**: Recurso conflictivo ya existe
3105
+ - **Acción**: Resolver conflicto (renombrar, actualizar en lugar de crear)
3106
+ - **Ejemplos**:
3107
+
3108
+ ```bash
3109
+ # Error: Branch ya existe
3110
+ curl --request POST \
3111
+ --header "PRIVATE-TOKEN: <token>" \
3112
+ --data '{"branch": "main", "ref": "main"}' \
3113
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/branches"
3114
+ # Response: 409 {"message":"Branch already exists"}
3115
+
3116
+ # Error: Label duplicado
3117
+ curl --request POST \
3118
+ --header "PRIVATE-TOKEN: <token>" \
3119
+ --data '{"name": "bug", "color": "#FF0000"}' \
3120
+ --url "https://gitlab.example.com/api/v4/projects/123/labels"
3121
+ # Response: 409 {"message":"Label already exists"}
3122
+ ```
3123
+
3124
+ **Resolución**:
3125
+ 1. Verificar si el recurso ya existe antes de crear
3126
+ 2. Usar PUT/PATCH para actualizar en lugar de POST para crear
3127
+ 3. Usar nombres únicos para recursos nuevos
3128
+
3129
+ ---
3130
+
3131
+ **412 Precondition Failed**
3132
+ - **Significado**: Precondición del request falló
3133
+ - **Acción**: Verificar headers condicionales
3134
+ - **Ejemplo**:
3135
+
3136
+ ```bash
3137
+ # Error: Archivo modificado entre lectura y escritura
3138
+ curl --request PUT \
3139
+ --header "PRIVATE-TOKEN: <token>" \
3140
+ --header "If-Unmodified-Since: Wed, 01 Jan 2026 00:00:00 GMT" \
3141
+ --data '{"content": "new content"}' \
3142
+ --url "https://gitlab.example.com/api/v4/projects/123/repository/files/file.txt"
3143
+ # Response: 412 (archivo modificado después de la fecha)
3144
+ ```
3145
+
3146
+ **Resolución**:
3147
+ 1. Refrescar datos antes de actualizar
3148
+ 2. Usar `last_commit_id` para verificar versión
3149
+ 3. Implementar retry con datos actualizados
3150
+
3151
+ ---
3152
+
3153
+ **422 Unprocessable Entity**
3154
+ - **Significado**: Entidad no procesable (validación falló)
3155
+ - **Acción**: Verificar validación de campos
3156
+ - **Ejemplos**:
3157
+
3158
+ ```bash
3159
+ # Error: Email inválido
3160
+ curl --request POST \
3161
+ --header "PRIVATE-TOKEN: <token>" \
3162
+ --data '{"email": "invalid-email", "name": "User"}' \
3163
+ --url "https://gitlab.example.com/api/v4/users"
3164
+ # Response: 422 {"message":{"email":["is invalid"]}}
3165
+
3166
+ # Error: Valor fuera de rango
3167
+ curl --request PUT \
3168
+ --header "PRIVATE-TOKEN: <token>" \
3169
+ --data '{"per_page": 150}' \
3170
+ --url "https://gitlab.example.com/api/v4/projects/123"
3171
+ # Response: 422 {"message":{"per_page":["must be less than or equal to 100"]}}
3172
+ ```
3173
+
3174
+ **Resolución**:
3175
+ 1. Leer mensaje de error detallado
3176
+ 2. Verificar formato de campos (email, URL, etc.)
3177
+ 3. Verificar rangos de valores numéricos
3178
+ 4. Verificar longitud de strings
3179
+ 5. Verificar valores enum válidos
3180
+
3181
+ ---
3182
+
3183
+ **429 Too Many Requests**
3184
+ - **Significado**: Rate limit excedido
3185
+ - **Acción**: Esperar y reintentar con backoff exponencial
3186
+ - **Ejemplo**:
3187
+
3188
+ ```bash
3189
+ curl --header "PRIVATE-TOKEN: <token>" \
3190
+ --url "https://gitlab.example.com/api/v4/projects/123"
3191
+ # Response: 429 {"message":"429 Too Many Requests"}
3192
+ ```
3193
+
3194
+ **Resolución con retry automático**:
3195
+
3196
+ ```bash
3197
+ #!/bin/bash
3198
+
3199
+ retry_with_backoff() {
3200
+ local url="$1"
3201
+ local max_attempts=5
3202
+ local timeout=1
3203
+ local attempt=1
3204
+
3205
+ while [ $attempt -le $max_attempts ]; do
3206
+ echo "Attempt $attempt of $max_attempts..."
3207
+
3208
+ HTTP_CODE=$(curl --write-out '%{http_code}' \
3209
+ --silent \
3210
+ --output response.json \
3211
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3212
+ "$url")
3213
+
3214
+ if [ "$HTTP_CODE" -eq 429 ]; then
3215
+ echo "Rate limited. Waiting ${timeout}s before retry..."
3216
+ sleep $timeout
3217
+ timeout=$((timeout * 2)) # Backoff exponencial
3218
+ attempt=$((attempt + 1))
3219
+ elif [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
3220
+ echo "Success!"
3221
+ cat response.json
3222
+ return 0
3223
+ else
3224
+ echo "Error: HTTP $HTTP_CODE"
3225
+ cat response.json
3226
+ return 1
3227
+ fi
3228
+ done
3229
+
3230
+ echo "Max retries exceeded"
3231
+ return 1
3232
+ }
3233
+
3234
+ # Uso
3235
+ retry_with_backoff "https://gitlab.example.com/api/v4/projects/123"
3236
+ ```
3237
+
3238
+ **Mejores prácticas**:
3239
+ 1. Implementar backoff exponencial (1s, 2s, 4s, 8s, 16s)
3240
+ 2. Verificar headers `RateLimit-*` en respuestas
3241
+ 3. Usar keyset pagination en lugar de offset para grandes datasets
3242
+ 4. Batch operations cuando sea posible (GraphQL multiplex)
3243
+ 5. Cachear respuestas cuando sea apropiado
3244
+
3245
+ ---
3246
+
3247
+ #### 5xx - Errores del Servidor
3248
+
3249
+ **500 Internal Server Error**
3250
+ - **Significado**: Error interno del servidor
3251
+ - **Acción**: Reportar a administrador, reintentar más tarde
3252
+ - **Ejemplo**:
3253
+
3254
+ ```bash
3255
+ curl --header "PRIVATE-TOKEN: <token>" \
3256
+ --url "https://gitlab.example.com/api/v4/projects/123"
3257
+ # Response: 500 {"message":"500 Internal Server Error"}
3258
+ ```
3259
+
3260
+ **Resolución**:
3261
+ 1. Verificar logs del servidor (si tienes acceso)
3262
+ 2. Reintentar después de unos minutos
3263
+ 3. Reportar a administrador con detalles del request
3264
+ 4. Verificar status de GitLab.com (si aplica)
3265
+
3266
+ ---
3267
+
3268
+ **503 Service Unavailable**
3269
+ - **Significado**: Servidor temporalmente sobrecargado
3270
+ - **Acción**: Reintentar con backoff
3271
+ - **Ejemplo**:
3272
+
3273
+ ```bash
3274
+ curl --header "PRIVATE-TOKEN: <token>" \
3275
+ --url "https://gitlab.example.com/api/v4/projects/123"
3276
+ # Response: 503 {"message":"503 Service Unavailable"}
3277
+ ```
3278
+
3279
+ **Resolución**:
3280
+ 1. Esperar y reintentar (similar a 429)
3281
+ 2. Verificar header `Retry-After` si está presente
3282
+ 3. Reducir frecuencia de requests
3283
+ 4. Verificar status de GitLab.com
3284
+
3285
+ ---
3286
+
3287
+ ### Debugging Avanzado
3288
+
3289
+ #### Capturar información completa del request:
3290
+
3291
+ ```bash
3292
+ #!/bin/bash
3293
+
3294
+ debug_api_call() {
3295
+ local method="${1:-GET}"
3296
+ local url="$2"
3297
+ local data="$3"
3298
+
3299
+ echo "=== API Debug Info ==="
3300
+ echo "Method: $method"
3301
+ echo "URL: $url"
3302
+ echo "Timestamp: $(date -Iseconds)"
3303
+ echo ""
3304
+
3305
+ if [ -n "$data" ]; then
3306
+ curl --request "$method" \
3307
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3308
+ --header "Content-Type: application/json" \
3309
+ --data "$data" \
3310
+ --verbose \
3311
+ --include \
3312
+ --output response_body.txt \
3313
+ --write-out "\n=== Response Info ===\nHTTP Code: %{http_code}\nTotal Time: %{time_total}s\nSize: %{size_download} bytes\n" \
3314
+ "$url" 2> request_debug.txt
3315
+ else
3316
+ curl --request "$method" \
3317
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3318
+ --verbose \
3319
+ --include \
3320
+ --output response_body.txt \
3321
+ --write-out "\n=== Response Info ===\nHTTP Code: %{http_code}\nTotal Time: %{time_total}s\nSize: %{size_download} bytes\n" \
3322
+ "$url" 2> request_debug.txt
3323
+ fi
3324
+
3325
+ echo ""
3326
+ echo "=== Request Debug ==="
3327
+ cat request_debug.txt
3328
+ echo ""
3329
+ echo "=== Response Body ==="
3330
+ cat response_body.txt | jq '.' 2>/dev/null || cat response_body.txt
3331
+ }
3332
+
3333
+ # Uso
3334
+ debug_api_call GET "https://gitlab.example.com/api/v4/projects/123"
3335
+ debug_api_call POST "https://gitlab.example.com/api/v4/projects/123/issues" \
3336
+ '{"title": "Test issue"}'
3337
+ ```
3338
+
3339
+ #### Verificar headers de respuesta:
3340
+
3341
+ ```bash
3342
+ # Capturar solo headers
3343
+ curl --head \
3344
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3345
+ --url "https://gitlab.example.com/api/v4/projects/123"
3346
+
3347
+ # Headers importantes:
3348
+ # - RateLimit-Limit: Límite de requests
3349
+ # - RateLimit-Remaining: Requests restantes
3350
+ # - RateLimit-Reset: Timestamp de reset
3351
+ # - X-Total: Total de items (paginación)
3352
+ # - X-Total-Pages: Total de páginas
3353
+ # - X-Page: Página actual
3354
+ # - Link: Links de paginación
3355
+ ```
3356
+
3357
+ #### Verificar conectividad y DNS:
3358
+
3359
+ ```bash
3360
+ # Verificar resolución DNS
3361
+ nslookup gitlab.example.com
3362
+
3363
+ # Verificar conectividad
3364
+ ping -c 4 gitlab.example.com
3365
+
3366
+ # Verificar SSL/TLS
3367
+ openssl s_client -connect gitlab.example.com:443 -servername gitlab.example.com
3368
+
3369
+ # Trace completo
3370
+ curl --trace-ascii trace.txt \
3371
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3372
+ --url "https://gitlab.example.com/api/v4/projects/123"
3373
+ ```
3374
+
3375
+ ---
3376
+
3377
+ ### Errores Específicos de GraphQL
3378
+
3379
+ GraphQL siempre retorna HTTP 200, pero puede contener errores en la respuesta:
3380
+
3381
+ #### Error de autenticación:
3382
+ ```json
3383
+ {
3384
+ "errors": [
3385
+ {
3386
+ "message": "Invalid token"
3387
+ }
3388
+ ]
3389
+ }
3390
+ ```
3391
+
3392
+ #### Error de query inválida:
3393
+ ```json
3394
+ {
3395
+ "errors": [
3396
+ {
3397
+ "message": "Field 'invalidField' doesn't exist on type 'Project'",
3398
+ "locations": [{"line": 3, "column": 5}],
3399
+ "path": ["project", "invalidField"]
3400
+ }
3401
+ ]
3402
+ }
3403
+ ```
3404
+
3405
+ #### Error de complejidad excedida:
3406
+ ```json
3407
+ {
3408
+ "errors": [
3409
+ {
3410
+ "message": "Query has complexity of 251, which exceeds max complexity of 250"
3411
+ }
3412
+ ]
3413
+ }
3414
+ ```
3415
+
3416
+ **Resolución**:
3417
+ 1. Verificar sintaxis de query
3418
+ 2. Verificar campos existen en schema
3419
+ 3. Reducir complejidad (menos campos, menos niveles de anidamiento)
3420
+ 4. Usar paginación para limitar resultados
3421
+
3422
+ #### Verificar errores en GraphQL:
3423
+
3424
+ ```bash
3425
+ #!/bin/bash
3426
+
3427
+ graphql_query() {
3428
+ local query="$1"
3429
+
3430
+ RESPONSE=$(curl --request POST \
3431
+ --silent \
3432
+ --header "Authorization: Bearer $GITLAB_TOKEN" \
3433
+ --header "Content-Type: application/json" \
3434
+ --data "{\"query\": \"$query\"}" \
3435
+ --url "https://gitlab.com/api/graphql")
3436
+
3437
+ # Verificar si hay errores
3438
+ if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
3439
+ echo "❌ GraphQL Errors:"
3440
+ echo "$RESPONSE" | jq '.errors'
3441
+ return 1
3442
+ else
3443
+ echo "✅ Success:"
3444
+ echo "$RESPONSE" | jq '.data'
3445
+ return 0
3446
+ fi
3447
+ }
3448
+
3449
+ # Uso
3450
+ graphql_query 'query {currentUser {name}}'
3451
+ ```
3452
+
3453
+ ---
3454
+
3455
+ ### Spam Detection
3456
+
3457
+ #### REST API - Sin CAPTCHA:
3458
+
3459
+ ```json
3460
+ {
3461
+ "message": {
3462
+ "error": "Your snippet has been recognized as spam and has been discarded."
3463
+ }
3464
+ }
3465
+ ```
3466
+
3467
+ **Resolución**: Modificar contenido para que no sea detectado como spam.
3468
+
3469
+ #### REST API - Con CAPTCHA:
3470
+
3471
+ ```json
3472
+ {
3473
+ "needs_captcha_response": true,
3474
+ "spam_log_id": 42,
3475
+ "captcha_site_key": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
3476
+ "message": {
3477
+ "error": "Your snippet has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."
3478
+ }
3479
+ }
3480
+ ```
3481
+
3482
+ **Resolución**:
3483
+
3484
+ ```bash
3485
+ #!/bin/bash
3486
+
3487
+ # 1. Obtener CAPTCHA response (Google reCAPTCHA v2)
3488
+ # Esto requiere interacción humana en browser
3489
+ # https://developers.google.com/recaptcha/docs/display
3490
+
3491
+ # 2. Reenviar con headers de CAPTCHA
3492
+ CAPTCHA_RESPONSE="<response-from-recaptcha>"
3493
+ SPAM_LOG_ID="42"
3494
+
3495
+ curl --request POST \
3496
+ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3497
+ --header "X-GitLab-Captcha-Response: $CAPTCHA_RESPONSE" \
3498
+ --header "X-GitLab-Spam-Log-Id: $SPAM_LOG_ID" \
3499
+ --header "Content-Type: application/json" \
3500
+ --data '{"title": "My snippet", "content": "Content"}' \
3501
+ --url "https://gitlab.example.com/api/v4/snippets"
3502
+ ```
3503
+
3504
+ #### GraphQL - Con CAPTCHA:
3505
+
3506
+ ```json
3507
+ {
3508
+ "errors": [
3509
+ {
3510
+ "message": "Request denied. Solve CAPTCHA challenge and retry",
3511
+ "locations": [{"line": 6, "column": 7}],
3512
+ "path": ["updateSnippet"],
3513
+ "extensions": {
3514
+ "needsCaptchaResponse": true,
3515
+ "captchaSiteKey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
3516
+ "spamLogId": 67
3517
+ }
3518
+ }
3519
+ ],
3520
+ "data": {
3521
+ "updateSnippet": {
3522
+ "snippet": null
3523
+ }
3524
+ }
3525
+ }
3526
+ ```
3527
+
3528
+ **Resolución**:
3529
+
3530
+ ```bash
3531
+ CAPTCHA_RESPONSE="<response-from-recaptcha>"
3532
+ SPAM_LOG_ID="67"
3533
+
3534
+ curl --request POST \
3535
+ --header "Authorization: Bearer $GITLAB_TOKEN" \
3536
+ --header "Content-Type: application/json" \
3537
+ --header "X-GitLab-Captcha-Response: $CAPTCHA_RESPONSE" \
3538
+ --header "X-GitLab-Spam-Log-Id: $SPAM_LOG_ID" \
3539
+ --data-binary '{"query": "mutation {updateSnippet(input: {id: \"gid://gitlab/Snippet/123\", title: \"Updated\"}) {snippet {id} errors}}"}' \
3540
+ --url "https://gitlab.com/api/graphql"
3541
+ ```
3542
+
3543
+ ---
3544
+
3545
+ ### Error 404 con Reverse Proxy
3546
+
3547
+ **Problema**: URLs con caracteres especiales (/, ?, @) causan 404 cuando hay reverse proxy.
3548
+
3549
+ **Causa**: Proxy decodifica URL-encoded characters antes de pasar a GitLab.
3550
+
3551
+ **Síntomas**:
3552
+ - Editor extensions no funcionan
3553
+ - glab CLI falla con paths
3554
+ - API calls con URL-encoded params retornan 404
3555
+
3556
+ **Solución Apache**:
3557
+
3558
+ ```apache
3559
+ <VirtualHost *:443>
3560
+ ServerName git.example.com
3561
+
3562
+ # CRÍTICO: Agregar esta línea
3563
+ AllowEncodedSlashes NoDecode
3564
+
3565
+ <Location />
3566
+ # CRÍTICO: Agregar flag nocanon
3567
+ ProxyPass http://127.0.0.1:8080/ nocanon
3568
+ ProxyPassReverse http://127.0.0.1:8080/
3569
+ </Location>
3570
+ </VirtualHost>
3571
+ ```
3572
+
3573
+ **Solución NGINX**:
3574
+
3575
+ ```nginx
3576
+ server {
3577
+ server_name git.example.com;
3578
+
3579
+ location / {
3580
+ # CRÍTICO: No decodificar automáticamente
3581
+ proxy_pass http://127.0.0.1:8080;
3582
+ proxy_set_header Host $host;
3583
+ proxy_set_header X-Real-IP $remote_addr;
3584
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
3585
+ proxy_set_header X-Forwarded-Proto $scheme;
3586
+ }
3587
+ }
3588
+ ```
3589
+
3590
+ **Verificación**:
3591
+
3592
+ ```bash
3593
+ # Debe funcionar sin 404
3594
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3595
+ --url "https://git.example.com/api/v4/projects/grupo%2Fproyecto"
3596
+
3597
+ # También debe funcionar
3598
+ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
3599
+ --url "https://git.example.com/api/v4/projects/123/repository/files/src%2Fapp.js?ref=main"
3600
+ ```
3601
+
3602
+ ---
3603
+
3604
+ ## Estructura Modular Recomendada
3605
+
3606
+ Para organizar una skill operativa de GitLab API, se recomienda la siguiente estructura modular:
3607
+
3608
+ ```
3609
+ gitlab-api-skill/
3610
+ ├── README.md # Índice principal y guía de uso
3611
+ ├── AUTHENTICATION.md # Guía de autenticación (tokens, scopes, headers)
3612
+ ├── COMMON_PATTERNS.md # Patrones comunes (paginación, filtrado, errores)
3613
+ ├── TROUBLESHOOTING.md # Guía de troubleshooting y códigos HTTP
3614
+
3615
+ ├── rest/
3616
+ │ ├── README.md # Introducción a REST API
3617
+ │ ├── projects.md # Operaciones con proyectos
3618
+ │ ├── groups.md # Operaciones con grupos
3619
+ │ ├── issues.md # Operaciones con issues
3620
+ │ ├── merge-requests.md # Operaciones con merge requests
3621
+ │ ├── pipelines.md # Operaciones con pipelines
3622
+ │ ├── jobs.md # Operaciones con jobs
3623
+ │ ├── repository.md # Operaciones con repositorio
3624
+ │ ├── files.md # Operaciones con archivos
3625
+ │ ├── releases.md # Operaciones con releases
3626
+ │ ├── webhooks.md # Operaciones con webhooks
3627
+ │ ├── variables.md # Operaciones con variables CI/CD
3628
+ │ ├── users.md # Operaciones con usuarios
3629
+ │ ├── labels.md # Operaciones con labels
3630
+ │ ├── milestones.md # Operaciones con milestones
3631
+ │ ├── runners.md # Operaciones con runners
3632
+ │ ├── registry.md # Operaciones con container registry
3633
+ │ └── deployments.md # Operaciones con deployments
3634
+
3635
+ ├── graphql/
3636
+ │ ├── README.md # Introducción a GraphQL API
3637
+ │ ├── queries/
3638
+ │ │ ├── projects.md # Queries de proyectos
3639
+ │ │ ├── issues.md # Queries de issues
3640
+ │ │ ├── merge-requests.md # Queries de merge requests
3641
+ │ │ ├── pipelines.md # Queries de pipelines
3642
+ │ │ ├── users.md # Queries de usuarios
3643
+ │ │ └── advanced.md # Queries avanzadas (multiplex, complexity)
3644
+ │ └── mutations/
3645
+ │ ├── projects.md # Mutations de proyectos
3646
+ │ ├── issues.md # Mutations de issues
3647
+ │ ├── merge-requests.md # Mutations de merge requests
3648
+ │ ├── pipelines.md # Mutations de pipelines
3649
+ │ └── advanced.md # Mutations avanzadas
3650
+
3651
+ ├── glab/
3652
+ │ ├── README.md # Introducción a glab CLI
3653
+ │ ├── setup.md # Instalación y configuración
3654
+ │ ├── projects.md # Comandos de proyectos
3655
+ │ ├── issues.md # Comandos de issues
3656
+ │ ├── merge-requests.md # Comandos de merge requests
3657
+ │ ├── pipelines.md # Comandos de pipelines
3658
+ │ ├── api.md # Uso de glab api
3659
+ │ └── scripting.md # Scripts y automatización
3660
+
3661
+ ├── examples/
3662
+ │ ├── bash/
3663
+ │ │ ├── create-issue.sh # Script completo: crear issue
3664
+ │ │ ├── create-mr.sh # Script completo: crear MR
3665
+ │ │ ├── trigger-pipeline.sh # Script completo: ejecutar pipeline
3666
+ │ │ ├── bulk-operations.sh # Script: operaciones en bulk
3667
+ │ │ └── monitoring.sh # Script: monitoreo de pipelines
3668
+ │ ├── python/
3669
+ │ │ ├── gitlab_client.py # Cliente Python para GitLab API
3670
+ │ │ ├── create_issue.py # Ejemplo: crear issue
3671
+ │ │ ├── create_mr.py # Ejemplo: crear MR
3672
+ │ │ └── pipeline_monitor.py # Ejemplo: monitorear pipelines
3673
+ │ └── graphql/
3674
+ │ ├── queries.graphql # Colección de queries útiles
3675
+ │ └── mutations.graphql # Colección de mutations útiles
3676
+
3677
+ └── reference/
3678
+ ├── status-codes.md # Referencia completa de códigos HTTP
3679
+ ├── rate-limits.md # Información sobre rate limits
3680
+ ├── pagination.md # Guía detallada de paginación
3681
+ └── global-ids.md # Guía de Global IDs en GraphQL
3682
+ ```
3683
+
3684
+ ### Contenido de cada archivo
3685
+
3686
+ #### README.md (Principal)
3687
+
3688
+ ```markdown
3689
+ # GitLab API & CLI - Skill Operativa
3690
+
3691
+ Guía completa para interactuar con GitLab mediante API REST, GraphQL y CLI (glab).
3692
+
3693
+ ## Índice
3694
+
3695
+ ### Guías Generales
3696
+ - [Autenticación](AUTHENTICATION.md) - Tokens, scopes, headers
3697
+ - [Patrones Comunes](COMMON_PATTERNS.md) - Paginación, filtrado, errores
3698
+ - [Troubleshooting](TROUBLESHOOTING.md) - Códigos HTTP y resolución
3699
+
3700
+ ### REST API
3701
+ - [Introducción](rest/README.md)
3702
+ - [Projects](rest/projects.md) - Crear, listar, actualizar proyectos
3703
+ - [Issues](rest/issues.md) - Gestión de issues
3704
+ - [Merge Requests](rest/merge-requests.md) - Gestión de MRs
3705
+ - [Pipelines](rest/pipelines.md) - CI/CD pipelines
3706
+ - [Jobs](rest/jobs.md) - CI/CD jobs
3707
+ - [Repository](rest/repository.md) - Branches, tags, commits
3708
+ - [Files](rest/files.md) - Operaciones con archivos
3709
+ - [Y más...](rest/)
3710
+
3711
+ ### GraphQL API
3712
+ - [Introducción](graphql/README.md)
3713
+ - [Queries](graphql/queries/) - Consultas de datos
3714
+ - [Mutations](graphql/mutations/) - Modificaciones de datos
3715
+
3716
+ ### glab CLI
3717
+ - [Introducción](glab/README.md)
3718
+ - [Setup](glab/setup.md) - Instalación y configuración
3719
+ - [Comandos por feature](glab/)
3720
+
3721
+ ### Ejemplos
3722
+ - [Bash Scripts](examples/bash/)
3723
+ - [Python](examples/python/)
3724
+ - [GraphQL](examples/graphql/)
3725
+
3726
+ ### Referencia
3727
+ - [Códigos de Estado HTTP](reference/status-codes.md)
3728
+ - [Rate Limits](reference/rate-limits.md)
3729
+ - [Paginación](reference/pagination.md)
3730
+ - [Global IDs](reference/global-ids.md)
3731
+
3732
+ ## Inicio Rápido
3733
+
3734
+ ### REST API
3735
+ \`\`\`bash
3736
+ # Listar proyectos
3737
+ curl --header "PRIVATE-TOKEN: <token>" \\
3738
+ --url "https://gitlab.example.com/api/v4/projects"
3739
+ \`\`\`
3740
+
3741
+ ### GraphQL API
3742
+ \`\`\`bash
3743
+ curl --request POST \\
3744
+ --header "Authorization: Bearer <token>" \\
3745
+ --header "Content-Type: application/json" \\
3746
+ --data '{"query": "query {currentUser {name}}"}' \\
3747
+ --url "https://gitlab.com/api/graphql"
3748
+ \`\`\`
3749
+
3750
+ ### glab CLI
3751
+ \`\`\`bash
3752
+ # Autenticación
3753
+ glab auth login
3754
+
3755
+ # Listar issues
3756
+ glab issue list
3757
+ \`\`\`
3758
+
3759
+ ## Recursos Oficiales
3760
+
3761
+ - [GitLab API Docs](https://docs.gitlab.com/api/)
3762
+ - [GraphQL API Reference](https://docs.gitlab.com/api/graphql/reference/)
3763
+ - [glab CLI Docs](https://docs.gitlab.com/cli/)
3764
+ ```
3765
+
3766
+ #### rest/projects.md (Ejemplo)
3767
+
3768
+ ```markdown
3769
+ # Projects API - REST
3770
+
3771
+ Operaciones con proyectos de GitLab mediante REST API.
3772
+
3773
+ ## Endpoints
3774
+
3775
+ - `GET /projects` - Listar proyectos
3776
+ - `GET /projects/:id` - Obtener proyecto
3777
+ - `POST /projects` - Crear proyecto
3778
+ - `PUT /projects/:id` - Actualizar proyecto
3779
+ - `DELETE /projects/:id` - Eliminar proyecto
3780
+ - `POST /projects/:id/archive` - Archivar proyecto
3781
+ - `POST /projects/:id/unarchive` - Desarchivar proyecto
3782
+
3783
+ ## Ejemplos
3784
+
3785
+ ### Listar proyectos
3786
+
3787
+ \`\`\`bash
3788
+ # Todos los proyectos
3789
+ curl --header "PRIVATE-TOKEN: <token>" \\
3790
+ --url "https://gitlab.example.com/api/v4/projects"
3791
+
3792
+ # Proyectos propios
3793
+ curl --header "PRIVATE-TOKEN: <token>" \\
3794
+ --url "https://gitlab.example.com/api/v4/projects?owned=true"
3795
+
3796
+ # Con paginación
3797
+ curl --header "PRIVATE-TOKEN: <token>" \\
3798
+ --url "https://gitlab.example.com/api/v4/projects?page=2&per_page=50"
3799
+ \`\`\`
3800
+
3801
+ ### Obtener proyecto
3802
+
3803
+ \`\`\`bash
3804
+ # Por ID
3805
+ curl --header "PRIVATE-TOKEN: <token>" \\
3806
+ --url "https://gitlab.example.com/api/v4/projects/123"
3807
+
3808
+ # Por path (URL-encoded)
3809
+ curl --header "PRIVATE-TOKEN: <token>" \\
3810
+ --url "https://gitlab.example.com/api/v4/projects/grupo%2Fproyecto"
3811
+ \`\`\`
3812
+
3813
+ ### Crear proyecto
3814
+
3815
+ \`\`\`bash
3816
+ curl --request POST \\
3817
+ --header "PRIVATE-TOKEN: <token>" \\
3818
+ --header "Content-Type: application/json" \\
3819
+ --data '{
3820
+ "name": "Mi Proyecto",
3821
+ "path": "mi-proyecto",
3822
+ "description": "Descripción",
3823
+ "visibility": "private",
3824
+ "initialize_with_readme": true
3825
+ }' \\
3826
+ --url "https://gitlab.example.com/api/v4/projects"
3827
+ \`\`\`
3828
+
3829
+ ## Ver también
3830
+
3831
+ - [Groups](groups.md)
3832
+ - [Repository](repository.md)
3833
+ - [Troubleshooting](../TROUBLESHOOTING.md)
3834
+ ```
3835
+
3836
+ ### Ventajas de esta estructura:
3837
+
3838
+ 1. **Modularidad**: Cada feature en su propio archivo
3839
+ 2. **Navegación fácil**: Índice claro en README principal
3840
+ 3. **Búsqueda rápida**: Archivos organizados por tipo de API
3841
+ 4. **Ejemplos prácticos**: Scripts completos en carpeta examples/
3842
+ 5. **Referencia rápida**: Información común en archivos dedicados
3843
+ 6. **Escalabilidad**: Fácil agregar nuevos endpoints/features
3844
+ 7. **Mantenimiento**: Actualizar un feature no afecta otros
3845
+ 8. **Cross-referencing**: Links entre archivos relacionados
3846
+
3847
+ ### Uso de la estructura:
3848
+
3849
+ ```bash
3850
+ # Buscar cómo crear un issue
3851
+ cat rest/issues.md | grep -A 10 "Crear issue"
3852
+
3853
+ # Ver todos los ejemplos de bash
3854
+ ls examples/bash/
3855
+
3856
+ # Buscar información sobre rate limits
3857
+ cat reference/rate-limits.md
3858
+
3859
+ # Ver troubleshooting de código 404
3860
+ cat TROUBLESHOOTING.md | grep -A 20 "404 Not Found"
3861
+ ```
3862
+
3863
+ ---
3864
+
3865
+ ## Conclusión
3866
+
3867
+ Esta guía proporciona:
3868
+
3869
+ ✅ **Resumen completo** de 7 URLs oficiales de GitLab Docs
3870
+ ✅ **Patrones de uso** para autenticación, paginación, errores y rate limits
3871
+ ✅ **25+ ejemplos concretos** distribuidos por features (projects, issues, MRs, pipelines, jobs, repository, files, releases, webhooks, variables, GraphQL queries/mutations, glab api)
3872
+ ✅ **Troubleshooting detallado** con códigos HTTP y resoluciones
3873
+ ✅ **Estructura modular** recomendada para organizar skill operativa
3874
+
3875
+ **Próximos pasos sugeridos:**
3876
+ 1. Implementar scripts de ejemplo en `examples/bash/`
3877
+ 2. Crear cliente Python reutilizable en `examples/python/`
3878
+ 3. Documentar casos de uso específicos por equipo/proyecto
3879
+ 4. Agregar ejemplos de integración CI/CD
3880
+ 5. Crear cheatsheet de comandos más usados
3881
+
3882
+ ---
3883
+
3884
+ **Fecha de última actualización**: 18 de febrero de 2026
3885
+ **Fuentes**: docs.gitlab.com/api/