From 77401a57ab08686b037766fd933f3f55b419dce5 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Sun, 15 Jun 2025 17:11:23 +0000 Subject: [PATCH] document api endpoints and webhooks --- docs/api.md | 601 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 601 insertions(+) create mode 100644 docs/api.md diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..fb0e2d9 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,601 @@ +# 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=` +- 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 + +```http +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:** + +```json +{ + "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 + +```http +GET /api/reviews/{review_id} +``` + +**Response:** + +```json +{ + "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 + +```http +GET /api/reviews/{review_id}/deliberation +``` + +**Response:** + +```json +{ + "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 + +```http +POST /api/reviews +``` + +**Request Body:** + +```json +{ + "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):** + +```json +{ + "status": "queued", + "job_id": "job-abc123", + "review_id": null, + "message": "Review queued with job ID job-abc123" +} +``` + +#### Get Metrics + +```http +GET /api/reviews/metrics +``` + +**Response:** + +```json +{ + "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 + +```http +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:** + +```json +{ + "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 + +```http +GET /api/conversations/{conversation_id} +``` + +#### Get Conversation for Review + +```http +GET /api/conversations/review/{review_id} +``` + +Returns `null` if no conversation exists for the review. + +### Health Endpoints + +#### Basic Health Check + +```http +GET /health +``` + +**Response:** + +```json +{ + "status": "healthy", + "version": "0.5.0" +} +``` + +#### Readiness Check + +```http +GET /health/ready +``` + +**Response:** + +```json +{ + "status": "ready", + "components": { + "database": {"status": "healthy"}, + "redis": {"status": "healthy"}, + "worker": {"status": "healthy", "queue_size": 3} + } +} +``` + +#### Liveness Probe + +```http +GET /health/live +``` + +**Response:** + +```json +{ + "status": "alive" +} +``` + +#### Prometheus Metrics + +```http +GET /metrics +``` + +Returns Prometheus-formatted metrics. + +## Webhook Endpoints + +### GitHub Webhook + +```http +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 reopened +- `issue_comment` — Comment created on PR (for follow-up questions) + +#### Pull Request Payload + +```json +{ + "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 + +```json +{ + "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:** + +```json +{"status": "queued", "job_id": "abc123", "repository": "owner/repo", "pr_number": 42} +``` + +```json +{"status": "ignored", "reason": "Event type 'push' not processed"} +``` + +```json +{"status": "duplicate", "reason": "Review already queued for this commit"} +``` + +### GitLab Webhook + +```http +POST /webhooks/gitlab +``` + +**Headers:** + +| Header | Description | +|--------|-------------| +| `X-GitLab-Token` | Verification token | + +**Supported Events:** + +- `merge_request` — MR opened, reopened, or updated +- `note` — Comment (note) on MR (for follow-up questions) + +#### Merge Request Payload + +```json +{ + "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 + +```json +{ + "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 + +```json +{ + "detail": "Missing required fields" +} +``` + +### 401 Unauthorized + +```json +{ + "detail": "Invalid signature" +} +``` + +### 404 Not Found + +```json +{ + "detail": "Review abc123 not found" +} +``` + +### 422 Validation Error + +```json +{ + "detail": [ + { + "loc": ["body", "pr_number"], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +### 500 Internal Server Error + +```json +{ + "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`: + +```bash +ARBITER_CORS_ORIGINS=["http://localhost:3000","https://dashboard.example.com"] +``` + +The API allows: +- Specified origins +- All standard headers +- Credentials +- GET, POST, PUT, DELETE methods