API Reference¶
This document summarizes all HTTP endpoints, schemas, authentication, rate limits, errors, and key flows for the service.
Base URL¶
- Default host/port: http://localhost:3000 (from service config defaults)
- API prefix: /api
- Health check has no /api prefix: /health
Authentication¶
- Admin API: require a shared token from ADMIN_API_TOKEN.
- Preferred: Authorization: Bearer
- Also accepted: X-Admin-API-Token:
- GitHub Webhook signature: HMAC-SHA256 using the configured webhook secret.
- Required headers: X-Hub-Signature-256: sha256=
, X-GitHub-Event, X-GitHub-Delivery - Only issue_comment events are processed; others return { status: "ignored", reason: "unsupported_event" }.
Rate Limiting¶
- Global IP-based limit (middleware createRateLimitMiddleware):
- Max requests: 5 per 60 seconds by default (configurable via serviceConfig.rateLimit).
- Key: client IP.
- Exceeding the limit returns HTTP 429 via Fastify rate-limit plugin.
- Repository-level webhook limit: the issue comment handler also enforces a per-repo limit (returns HTTP 429 with RATE_LIMITED when exhausted).
Error Responses¶
Standard envelope: { "error": { "code": string, "message": string, "details"?: any } } - 400 VALIDATION_ERROR – Zod or custom validation failures - 401 UNAUTHORIZED – missing/invalid admin token or webhook signature - 403 FORBIDDEN - 404 NOT_FOUND - 429 RATE_LIMITED – includes resetAt (ISO) and remaining - 500 INTERNAL_SERVER_ERROR
Endpoints¶
Use BASE_URL=http://localhost:3000 unless configured otherwise.
Health¶
- GET /health
- Auth: none
- Response: { status, timestamp, uptime, version?, components: { database: { status, latency?, error? }, redis: { status, latency?, error? } } }
- Example:
Runs¶
Create run¶
- POST /api/runs
- Schema: createRunRequestSchema (payloadSchema)
- Fields: run_id (string), source ("pr"|"web"), repo { full_name, clone_url }, comment_body, comment_raw_body, pr (required when source="pr": { number, comment_id, head_ref, head_sha, base_ref }), web (required when source="web": { web_conversation_id, user_id }).
- Response 201 (createRunResponseSchema): { run_id, status, created_at, artifacts_path, timestamp }
- Example (PR source):
curl -sS -X POST "$BASE_URL/api/runs" \ -H "Content-Type: application/json" \ -d '{ "run_id": "2026-01-07-abc123", "source": "pr", "repo": {"full_name": "org/repo", "clone_url": "https://github.com/org/repo.git"}, "comment_body": "/tinker please", "comment_raw_body": "/tinker please", "pr": {"number": 42, "comment_id": 1001, "head_ref": "feature", "head_sha": "deadbeef", "base_ref": "main"} }'
Get run by id¶
- GET /api/runs/:id
- Response 200 (getRunResponseSchema): run metadata including status, repo, timestamps, artifacts_path, summary, exit_code.
- Example:
List runs¶
- GET /api/runs?repo_full_name&status&limit&offset
- Query schema: listRunsQuerySchema (coerces numbers)
- Response 200 (listRunsResponseSchema): { runs: [...], total, limit, offset }
- Example:
GitHub Webhook¶
- POST /api/github/webhook
- Headers: X-GitHub-Event, X-Hub-Signature-256, X-GitHub-Delivery; body is raw buffer.
- Supported event: issue_comment only. Others return 200 ignored.
- Responses:
- 202 { status: "queued", run_id } when enqueued
- 429 RATE_LIMITED when repo limit hit
- 401 UNAUTHORIZED for bad signature
- 200 { status: "ignored", reason } for non-PR/missing prefix/unsupported event
- Example (signature placeholder):
Analytics¶
Aggregate metrics¶
- GET /api/analytics/metrics?metricName&aggregation&startDate&endDate
- Query schema: metricsQuerySchema
- metricName: one of duration_ms, exit_code, tokens_used, files_changed, cost_usd
- aggregation: sum|avg|min|max|count
- startDate, endDate: optional ISO strings
- Response 200: { metric, aggregation, value, startDate?, endDate? }
- Example:
Export metrics¶
- GET /api/analytics/export?metricName&startDate&endDate&limit&format
- Query schema: exportQuerySchema; format default json, limit <= 10000.
- Responses:
- JSON: { metricName, startDate?, endDate?, metrics: [{ id, run_id, metric_name, metric_value, recorded_at }] }
- CSV: sets Content-Type: text/csv
- Example (CSV):
Admin (token required)¶
All admin routes require Authorization: Bearer
- Pause repo: POST /api/admin/repos/:repoFullName/pause
- Body (pauseRepoSchema): { reason?: string, pausedUntil?: ISO datetime }
- Response: 204
- Unpause repo: POST /api/admin/repos/:repoFullName/unpause
- Response: 204
- List paused repos: GET /api/admin/repos/paused
- Response: array of { repo_full_name, paused_until|null, reason|null }
- Pause user for a repo: POST /api/admin/users/:userId/pause
- Body (pauseUserSchema): { repoFullName, reason?: string, pausedUntil?: ISO datetime }
- Response: 204
- Unpause user for a repo: POST /api/admin/users/:userId/unpause
- Body (unpauseUserSchema): { repoFullName }
- Response: 204
- List paused users: GET /api/admin/users/paused?repoFullName&userId
- Query (pausedUsersQuerySchema)
- Response: array with paused_until normalized to ISO or null
Sequence Diagrams (Mermaid)¶
Run creation¶
sequenceDiagram
participant Client
participant Fastify
participant RunsRoute as /api/runs
participant RunController
participant RunsUC as Runs Usecases
participant DB
Client->>Fastify: POST /api/runs (payload)
Fastify->>RunsRoute: validate body (zod)
RunsRoute->>RunController: createRunHandler
RunController->>RunsUC: createRun
RunsUC->>DB: insert run
DB-->>RunsUC: row
RunsUC-->>RunController: run
RunController->>RunsUC: startRun (async fire-and-forget)
RunController-->>Client: 201 CreateRunResponse
Webhook handling (issue_comment only)¶
sequenceDiagram
participant GitHub
participant Fastify
participant WebhookGate as preValidation
participant GHRoute as /api/github/webhook
participant GHController
participant Verify
participant Handle
participant DB
GitHub->>Fastify: POST webhook (raw body + headers)
Fastify->>WebhookGate: header checks (event, signature, delivery)
WebhookGate-->>Fastify: ok or 400
Fastify->>GHRoute: raw body parser
GHRoute->>GHController: webhookHandler
GHController->>Verify: verifyWebhook(secret, signature, body)
alt invalid signature
GHController-->>GitHub: 401 UNAUTHORIZED
else issue_comment
GHController->>Handle: handleIssueComment
alt repo rate limited
Handle-->>GHController: rate_limited
GHController-->>GitHub: 429 RATE_LIMITED
else queued
Handle->>DB: create run
Handle->>Handle: startRun async
GHController-->>GitHub: 202 {status: queued, run_id}
end
else unsupported event
GHController-->>GitHub: 200 {status: ignored}
end
Metrics aggregation¶
sequenceDiagram
participant Client
participant Fastify
participant AnalyticsRoute as /api/analytics/metrics
participant AnalyticsCtrl
participant MetricsUC
participant MetricsRepo
participant DB
Client->>Fastify: GET metrics query
Fastify->>AnalyticsRoute: zod query validation
AnalyticsRoute->>AnalyticsCtrl: getMetricsHandler
AnalyticsCtrl->>MetricsUC: aggregateMetrics
MetricsUC->>MetricsRepo: aggregate
MetricsRepo->>DB: SQL aggregation
DB-->>MetricsRepo: value
MetricsRepo-->>MetricsUC: value
MetricsUC-->>AnalyticsCtrl: result
AnalyticsCtrl-->>Client: 200 JSON