11 KiB
API Reference
Arbiter provides a REST API for managing reviews, monitoring metrics, and receiving webhook events from GitHub and GitLab.
Interactive Documentation
The API provides auto-generated interactive documentation:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI Schema:
http://localhost:8000/openapi.json
Base URL
All API endpoints are relative to the base URL:
http://localhost:8000
In production, use your configured domain with HTTPS.
Authentication
Webhook Authentication
Webhooks use signature/token verification:
GitHub:
- Header:
X-Hub-Signature-256 - Format:
sha256=<hmac_hex_digest> - Algorithm: HMAC-SHA256 using
ARBITER_GITHUB_WEBHOOK_SECRET
GitLab:
- Header:
X-GitLab-Token - Format: Plain token string
- Verification: Constant-time comparison with
ARBITER_GITLAB_WEBHOOK_TOKEN
API Authentication
The REST API currently does not require authentication. For production deployments, place it behind a reverse proxy with authentication or configure network-level access controls.
REST Endpoints
Reviews
List Reviews
GET /api/reviews
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number (1-indexed) |
page_size |
integer | 20 | Items per page (1-100) |
repository |
string | - | Filter by repository name |
status |
string | - | Filter by status (pending, processing, completed, failed) |
verdict |
string | - | Filter by verdict (approve, request_changes, comment) |
author |
string | - | Filter by PR author |
Response:
{
"items": [
{
"id": "abc123",
"repository": "owner/repo",
"pr_number": 42,
"pr_title": "Add new feature",
"author": "username",
"status": "completed",
"verdict": "request_changes",
"verdict_confidence": 0.92,
"finding_count": 5,
"critical_count": 1,
"high_count": 2,
"total_cost_usd": 0.12,
"created_at": "2025-06-04T10:00:00Z",
"completed_at": "2025-06-04T10:02:30Z"
}
],
"total": 150,
"page": 1,
"page_size": 20,
"pages": 8
}
Get Review Detail
GET /api/reviews/{review_id}
Response:
{
"id": "abc123",
"repository": "owner/repo",
"pr_number": 42,
"pr_title": "Add new feature",
"base_sha": "abc1234",
"head_sha": "def5678",
"author": "username",
"is_draft": false,
"status": "completed",
"verdict": "request_changes",
"verdict_confidence": 0.92,
"verdict_reasoning": "Critical security issue requires resolution",
"total_tokens": 15000,
"total_cost_usd": 0.12,
"tokens_by_agent": {"security": 5000, "style": 5000, "complexity": 5000},
"cost_by_agent": {"security": 0.04, "style": 0.04, "complexity": 0.04},
"created_at": "2025-06-04T10:00:00Z",
"started_at": "2025-06-04T10:00:05Z",
"completed_at": "2025-06-04T10:02:30Z",
"error_message": null,
"findings": [
{
"id": "finding-1",
"agent": "security",
"file": "src/auth.py",
"line_start": 42,
"line_end": 45,
"severity": "critical",
"confidence": 0.95,
"title": "SQL Injection Vulnerability",
"description": "User input is passed directly to SQL query",
"reasoning": "String interpolation in SQL queries allows injection",
"suggestion": "Use parameterized queries",
"references": ["https://owasp.org/..."],
"prompt_version": "security-v1.0"
}
],
"conflicts": [
{
"id": "conflict-1",
"finding_ids": ["finding-2", "finding-3"],
"nature": "opposing_recommendation",
"description": "Style suggests splitting function, complexity wants to keep it",
"severity_weight": 0.5,
"resolution": "Keep function together for readability",
"winning_finding_id": "finding-3"
}
]
}
Get Deliberation Log
GET /api/reviews/{review_id}/deliberation
Response:
{
"review_id": "abc123",
"steps": [
{
"id": "step-1",
"step_type": "agent_start",
"timestamp": "2025-06-04T10:00:10Z",
"description": "Security agent started review",
"details": {"agent": "security"},
"sequence": 1
},
{
"id": "step-2",
"step_type": "finding_added",
"timestamp": "2025-06-04T10:00:45Z",
"description": "Security agent found SQL injection",
"details": {"finding_id": "finding-1", "severity": "critical"},
"sequence": 2
},
{
"id": "step-3",
"step_type": "conflict_detected",
"timestamp": "2025-06-04T10:01:30Z",
"description": "Detected conflict between style and complexity",
"details": {"conflict_id": "conflict-1"},
"sequence": 3
},
{
"id": "step-4",
"step_type": "verdict_determined",
"timestamp": "2025-06-04T10:02:00Z",
"description": "Final verdict: request_changes",
"details": {"verdict": "request_changes", "confidence": 0.92},
"sequence": 4
}
]
}
Trigger Manual Review
POST /api/reviews
Request Body:
{
"repository": "owner/repo",
"pr_number": 42,
"base_sha": "abc1234",
"head_sha": "def5678",
"pr_title": "Add new feature",
"author": "username",
"is_draft": false,
"policy_name": "default",
"diff_content": "diff --git a/file.py b/file.py\n..."
}
Response (202 Accepted):
{
"status": "queued",
"job_id": "job-abc123",
"review_id": null,
"message": "Review queued with job ID job-abc123"
}
Get Metrics
GET /api/reviews/metrics
Response:
{
"total_reviews": 500,
"completed_reviews": 485,
"average_cost_usd": 0.15,
"verdict_counts": {
"approve": 300,
"request_changes": 150,
"comment": 35
},
"severity_counts": {
"critical": 50,
"high": 200,
"medium": 500,
"low": 1000,
"info": 300
},
"reviews_by_day": [
{"date": "2025-06-01", "count": 15},
{"date": "2025-06-02", "count": 22},
{"date": "2025-06-03", "count": 18}
],
"cost_by_agent": {
"security": 25.50,
"style": 20.00,
"complexity": 22.00
}
}
Conversations
List Conversations
GET /api/conversations
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
page_size |
integer | 20 | Items per page |
repository |
string | - | Filter by repository |
review_id |
string | - | Filter by review ID |
Response:
{
"items": [
{
"id": "conv-123",
"review_id": "abc123",
"platform": "github",
"repository": "owner/repo",
"pr_number": 42,
"message_count": 4,
"total_tokens": 2000,
"total_cost_usd": 0.02,
"started_at": "2025-06-04T11:00:00Z",
"last_activity": "2025-06-04T11:15:00Z"
}
],
"total": 25,
"page": 1,
"page_size": 20,
"pages": 2
}
Get Conversation Detail
GET /api/conversations/{conversation_id}
Get Conversation for Review
GET /api/conversations/review/{review_id}
Returns null if no conversation exists for the review.
Health Endpoints
Basic Health Check
GET /health
Response:
{
"status": "healthy",
"version": "0.5.0"
}
Readiness Check
GET /health/ready
Response:
{
"status": "ready",
"components": {
"database": {"status": "healthy"},
"redis": {"status": "healthy"},
"worker": {"status": "healthy", "queue_size": 3}
}
}
Liveness Probe
GET /health/live
Response:
{
"status": "alive"
}
Prometheus Metrics
GET /metrics
Returns Prometheus-formatted metrics.
Webhook Endpoints
GitHub Webhook
POST /webhooks/github
Headers:
| Header | Description |
|---|---|
X-Hub-Signature-256 |
HMAC-SHA256 signature |
X-GitHub-Event |
Event type (pull_request, issue_comment) |
Supported Events:
pull_request— PR opened, synchronized, or reopenedissue_comment— Comment created on PR (for follow-up questions)
Pull Request Payload
{
"action": "opened",
"pull_request": {
"number": 42,
"title": "Add new feature",
"draft": false,
"base": {"sha": "abc1234"},
"head": {"sha": "def5678"},
"user": {"login": "username"}
},
"repository": {
"full_name": "owner/repo"
}
}
Issue Comment Payload
{
"action": "created",
"issue": {
"number": 42,
"pull_request": {}
},
"comment": {
"id": 123456,
"body": "Can you explain the SQL injection warning?",
"user": {"login": "username"}
},
"repository": {
"full_name": "owner/repo"
}
}
Responses:
{"status": "queued", "job_id": "abc123", "repository": "owner/repo", "pr_number": 42}
{"status": "ignored", "reason": "Event type 'push' not processed"}
{"status": "duplicate", "reason": "Review already queued for this commit"}
GitLab Webhook
POST /webhooks/gitlab
Headers:
| Header | Description |
|---|---|
X-GitLab-Token |
Verification token |
Supported Events:
merge_request— MR opened, reopened, or updatednote— Comment (note) on MR (for follow-up questions)
Merge Request Payload
{
"object_kind": "merge_request",
"object_attributes": {
"iid": 42,
"title": "Add new feature",
"action": "open",
"target_branch": "main",
"work_in_progress": false,
"last_commit": {"id": "def5678"}
},
"project": {
"path_with_namespace": "owner/repo"
},
"user": {
"username": "username"
}
}
Note Payload
{
"object_kind": "note",
"object_attributes": {
"id": 123456,
"note": "Can you explain this warning?",
"noteable_type": "MergeRequest"
},
"merge_request": {
"iid": 42
},
"project": {
"path_with_namespace": "owner/repo"
},
"user": {
"username": "username"
}
}
Error Responses
All endpoints return standard HTTP error codes with JSON error details:
400 Bad Request
{
"detail": "Missing required fields"
}
401 Unauthorized
{
"detail": "Invalid signature"
}
404 Not Found
{
"detail": "Review abc123 not found"
}
422 Validation Error
{
"detail": [
{
"loc": ["body", "pr_number"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
500 Internal Server Error
{
"detail": "Internal server error"
}
Rate Limiting
The API applies rate limiting per client IP:
- Default: 60 requests per minute
- Configurable via
ARBITER_API_RATE_LIMIT_PER_MINUTE
Rate limit headers are included in responses:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
X-RateLimit-Reset: 1707048120
CORS
Cross-Origin Resource Sharing is configured via ARBITER_CORS_ORIGINS:
ARBITER_CORS_ORIGINS=["http://localhost:3000","https://dashboard.example.com"]
The API allows:
- Specified origins
- All standard headers
- Credentials
- GET, POST, PUT, DELETE methods