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 :

Cookie: jwt_token=<JWT>

Bearer PAT (clients API, CI, scripts) — Personal Access Token créé depuis /settings/tokens :

Authorization: Bearer <PAT>

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 :

{
    "error": "not_found",
    "message": "Le dépôt 'alice/myrepo' est introuvable",
    "status": 404
}

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 :

X-Total-Count: 142
X-Page: 2
X-Per-Page: 30

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.

// Requête
{ "ref": "main" }

// Réponse 201
{ "id": "...", "status": "pending", ... }

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"]);

Voir aussi