Skip to content

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:
    curl -sS "$BASE_URL/health"
    

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:
    curl -sS "$BASE_URL/api/runs/2026-01-07-abc123"
    

List runs

  • GET /api/runs?repo_full_name&status&limit&offset
  • Query schema: listRunsQuerySchema (coerces numbers)
  • Response 200 (listRunsResponseSchema): { runs: [...], total, limit, offset }
  • Example:
    curl -sS "$BASE_URL/api/runs?repo_full_name=org/repo&status=queued&limit=20&offset=0"
    

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):
    curl -sS -X POST "$BASE_URL/api/github/webhook" \
      -H "Content-Type: application/json" \
      -H "X-GitHub-Event: issue_comment" \
      -H "X-GitHub-Delivery: test-delivery" \
      -H "X-Hub-Signature-256: sha256=<computed>" \
      -d @webhook-payload.json
    

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:
    curl -sS "$BASE_URL/api/analytics/metrics?metricName=duration_ms&aggregation=avg&startDate=2026-01-01T00:00:00Z"
    

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):
    curl -sS "$BASE_URL/api/analytics/export?metricName=duration_ms&format=csv&limit=100"
    

Admin (token required)

All admin routes require Authorization: Bearer (or X-Admin-API-Token).

  • 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