API REST v1 — Référence¶
L'API REST gitrust est accessible sous le préfixe /api/v1/. Elle accepte et retourne du JSON. Elle partage les mêmes services que l'interface web SSR.
Documentation interactive : GET /api/docs sur toute instance gitrust.
Authentification¶
Deux méthodes sont acceptées sur tous les endpoints authentifiés :
Cookie JWT (navigateur) — défini automatiquement par le serveur après login :
Bearer PAT (clients API, CI, scripts) — Personal Access Token créé depuis /settings/tokens :
Les PAT supportent des scopes : repo:read, repo:write, issues:read, issues:write, pulls:read, pulls:write, ci:read, ci:write.
Un endpoint qui requiert repo:write rejette un PAT avec seulement repo:read avec 403 Forbidden.
Format des erreurs¶
Toutes les erreurs retournent ce format JSON :
Codes d'erreur standards :
error |
HTTP | Cause |
|---|---|---|
unauthorized |
401 | Token absent, expiré ou invalide |
forbidden |
403 | Permissions insuffisantes |
not_found |
404 | Ressource inexistante |
validation_error |
400 | Corps de requête invalide |
conflict |
409 | Contrainte d'unicité violée |
rate_limited |
429 | Trop de requêtes |
internal_error |
500 | Erreur serveur |
Pagination¶
Tous les endpoints de liste acceptent ?page=N&per_page=N (défaut : page=1, per_page=30, max per_page=100).
Headers de réponse :
Rate limiting¶
En cas de dépassement : 429 Too Many Requests avec Retry-After: <secondes>.
Endpoints¶
Authentification¶
POST /api/v1/auth/login¶
// Requête
{ "username": "alice", "password": "SecurePass123!" }
// Réponse 200 (sans 2FA)
{
"access_token": "<JWT>",
"refresh_token": "<token>",
"token_type": "Bearer",
"user": { "id": "...", "username": "alice", "email": "alice@example.com" }
}
// Réponse 200 (avec 2FA activé)
{ "requires_2fa": true, "challenge_token": "abc123..." }
POST /api/v1/auth/2fa/verify¶
// Requête
{ "challenge_token": "abc123...", "code": "123456" }
// Réponse 200
{ "access_token": "...", "refresh_token": "...", "user": { ... } }
POST /api/v1/auth/refresh¶
// Requête
{ "refresh_token": "<token>" }
// Réponse 200
{ "access_token": "<nouveau JWT>", "refresh_token": "<nouveau token>" }
GET /api/v1/auth/me¶
// Réponse 200
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"email_verified": true,
"roles": ["user"]
}
Utilisateur courant¶
GET /api/v1/user¶
Profil de l'utilisateur authentifié. Identique à GET /api/v1/auth/me.
GET /api/v1/user/repos¶
Liste les dépôts accessibles par l'utilisateur courant (possédés + partagés).
// Réponse 200
{
"items": [
{
"id": "...",
"owner": "alice",
"slug": "myrepo",
"description": "Mon premier dépôt",
"default_branch": "main",
"is_empty": false,
"is_public": false,
"created_at": "2026-03-25T10:00:00Z"
}
],
"total": 1,
"page": 1,
"per_page": 30
}
Dépôts¶
GET /api/v1/repos/{owner}/{repo}¶
// Réponse 200
{
"id": "...",
"owner": "alice",
"slug": "myrepo",
"description": "Mon dépôt",
"default_branch": "main",
"is_empty": false,
"is_public": true,
"created_at": "2026-03-25T10:00:00Z",
"updated_at": "2026-04-01T12:00:00Z"
}
GET /api/v1/repos/{owner}/{repo}/branches¶
// Réponse 200
{
"items": [
{ "name": "main", "is_default": true, "commit_sha": "abc123..." },
{ "name": "feat/42-fix", "is_default": false, "commit_sha": "def456..." }
],
"total": 2, "page": 1, "per_page": 30
}
GET /api/v1/repos/{owner}/{repo}/commits¶
Paramètres optionnels : ?ref=main&page=1&per_page=30
// Réponse 200
{
"items": [
{
"sha": "abc123...",
"message": "feat: ajouter la signature HMAC",
"author": { "name": "Alice", "email": "alice@example.com" },
"committed_at": "2026-04-01T09:00:00Z"
}
],
"total": 42, "page": 1, "per_page": 30
}
Issues¶
GET /api/v1/repos/{owner}/{repo}/issues¶
Paramètres : ?state=open&page=1&per_page=30
// Réponse 200
{
"items": [
{
"id": "...",
"number": 1,
"title": "Bug dans le parser",
"state": "open",
"author": "alice",
"labels": [
{ "name": "bug", "color": "#d73a4a", "type": "classification" }
],
"comment_count": 3,
"created_at": "2026-03-27T08:00:00Z"
}
],
"total": 5, "page": 1, "per_page": 30
}
POST /api/v1/repos/{owner}/{repo}/issues¶
// Requête
{ "title": "Nouveau bug", "body": "Description en Markdown..." }
// Réponse 201
{ "id": "...", "number": 6, "title": "Nouveau bug", "state": "open", ... }
PATCH /api/v1/repos/{owner}/{repo}/issues/{num}¶
// Requête (tous les champs optionnels)
{ "state": "closed" }
// Réponse 200
{ "id": "...", "number": 1, "state": "closed", ... }
Pull Requests¶
GET /api/v1/repos/{owner}/{repo}/pulls¶
Paramètres : ?state=open&page=1&per_page=30
POST /api/v1/repos/{owner}/{repo}/pulls¶
// Requête
{
"title": "feat: ajouter la signature HMAC",
"body": "Closes #42",
"source_branch": "feat/42-hmac",
"target_branch": "main"
}
// Réponse 201
{ "id": "...", "number": 3, "state": "open", ... }
POST /api/v1/repos/{owner}/{repo}/pulls/{num}/merge¶
// Requête
{ "merge_method": "merge" }
// merge_method: "merge" | "squash" | "rebase"
// Réponse 200
{ "merged": true, "merge_commit_sha": "abc123..." }
CI Pipelines¶
GET /api/v1/repos/{owner}/{repo}/ci/pipelines¶
Paramètres : ?page=1&per_page=30
// Réponse 200
{
"items": [
{
"id": "...",
"status": "success",
"trigger": "push",
"ref": "main",
"commit_sha": "abc123...",
"duration_ms": 45000,
"created_at": "2026-04-01T09:00:00Z"
}
],
"total": 12, "page": 1, "per_page": 30
}
POST /api/v1/repos/{owner}/{repo}/ci/pipelines/trigger¶
Déclenche un pipeline manuellement.
GET /api/v1/repos/{owner}/{repo}/ci/pipelines/{id}/logs¶
// Réponse 200
{
"items": [
{ "line": 1, "content": "$ cargo build --release", "stream": "stdout", "timestamp": "..." },
{ "line": 2, "content": " Compiling gitrust v0.1.0", "stream": "stdout", "timestamp": "..." }
],
"total": 248, "page": 1, "per_page": 100
}
Exemples clients¶
Lister les issues avec curl¶
curl -s \
-H "Authorization: Bearer ${GITRUST_PAT}" \
"https://demo.gitrust.eu/api/v1/repos/alice/myrepo/issues?state=open" \
| jq '.items[].title'
Créer une issue avec Python¶
import httpx
client = httpx.Client(
base_url="https://demo.gitrust.eu",
headers={"Authorization": f"Bearer {PAT}"},
)
resp = client.post(
"/api/v1/repos/alice/myrepo/issues",
json={"title": "Bug trouvé", "body": "Description..."},
)
resp.raise_for_status()
print(resp.json()["number"])
Créer une issue avec Rust¶
use reqwest::Client;
use serde_json::json;
let client = Client::new();
let resp = client
.post("https://demo.gitrust.eu/api/v1/repos/alice/myrepo/issues")
.bearer_auth(&pat)
.json(&json!({"title": "Bug", "body": "..."}))
.send()
.await?;
println!("Issue #{}", resp.json::<serde_json::Value>().await?["number"]);