API reference

Full reference for the on-demand review endpoint. Send a PR, get a verdict on the pull request. Everything you need to call POST /api/reviews from CI or anywhere you can fire a request.

1

Endpoint

Single endpoint, two modes:

POST  https://<host>/api/reviews          # async (default): ack fast, review runs in background
POST  https://<host>/api/reviews?wait=true  # sync: blocks until the review finishes
POST  https://<host>/api/reviews?sync=1     # alias for ?wait=true

<host> is hound-ai-review.polsia.app in production and localhost:3000 in dev.

2

Authentication

All requests must include the server-side API token as a Bearer header:

Authorization: Bearer <HOUND_API_TOKEN>

The expected token is whatever you set as the HOUND_API_TOKEN environment variable on the Hound deployment. The endpoint compares the presented header to process.env.HOUND_API_TOKEN using a constant-time comparison — keep the token out of source control and rotate it like any other shared secret.

Dev escape hatch. When NODE_ENV !== 'production' and HOUND_API_TOKEN is not set, the auth check is skipped. Don't rely on this in any deployed environment.

Required: the GitHub App must already be installed on the target repository (the endpoint uses your instance's GitHub App installation token server-side to fetch the PR and post comments — you don't pass a GitHub token in this request).

3

Request body

A JSON object with two required fields:

{
  "repo": "owner/name",
  "pr_number": 42
}

repo — required string. Must match ^[\w.-]+/[\w.-]+$ (typical "owner/name" GitHub repo path). The receiver validates this format and rejects anything that doesn't match.

pr_number — required integer ≥ 1. The pull request number on the target repo.

Source. The trigger source is set internally to "api" by the endpoint — clients do not pass it. The webhook pipeline uses "webhook"; this endpoint always records "api".

4

Response shapes

Async (default — fire-and-forget):

{
  "status": "queued",
  "repo": "owner/name",
  "pr_number": 42
}

The endpoint returns this as soon as the review is accepted. The actual review and the GitHub comment happen asynchronously — failures during the async run are logged server-side and do not surface here.

Sync (?wait=true or ?sync=1) — blocks until the review finishes:

{
  "status": "ok",
  "repo": "owner/name",
  "pr_number": 42,
  "review_id": 17,
  "comment_posted": true
}

review_id is the row id from the reviews table — use it to join to the full review if you want to inspect the stored verdict or findings later.

The human-readable verdict (summary + severity-tagged findings) is posted as a comment on the PR and persisted in the reviews table:

  • review_text (TEXT) — the prose summary and findings as written to the PR comment.
  • findings (JSONB) — the parsed severity-tagged findings list.

Neither response includes a latency field — the endpoint does not measure call duration. If you need timing, wrap the request yourself.

5

Errors

All errors come back as JSON with an error field:

// 401 — missing or invalid Bearer
{ "error": "Missing or malformed Authorization header" }
{ "error": "Invalid API token" }

// 400 — body failed validation
{ "error": "Invalid request body", "message": "`pr_number` must be an integer >= 1" }

// 500 — review pipeline failed while running (sync mode only)
{ "error": "Review failed", "message": "Failed to fetch PR metadata: 404 — ..." }

In async mode, a 200 acknowledges queuing even if the run later fails server-side — failure logs end up in the server console.

6

Rate limits

The endpoint itself does not enforce a per-token quota — there's no rate-limit middleware in front of POST /api/reviews. The binding constraints live downstream:

  • The upstream AI proxy (Anthropic via OpenAI-compatible API) rate-limits review completions.
  • GitHub's own per-installation rate limits apply — each call makes one GET /repos/{owner}/{repo}/pulls/{n} and (when posting) one POST /repos/{owner}/{repo}/issues/{n}/comments.

If you're going to fire this from CI on every commit, batch your triggers or rely on the GitHub App's automatic webhook pipeline instead.

7

Examples

Async (default):

curl -X POST https://<host>/api/reviews \
  -H "Authorization: Bearer $HOUND_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repo":"owner/name","pr_number":42}'

Sync (?wait=true):

curl -X POST "https://<host>/api/reviews?wait=true" \
  -H "Authorization: Bearer $HOUND_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repo":"owner/name","pr_number":42}'

JavaScript (Node / browser fetch):

const res = await fetch('https://<host>/api/reviews?wait=true', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.HOUND_API_TOKEN}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    repo: 'owner/name',
    pr_number: 42
  })
});

const body = await res.json();
// body.status === 'ok'  -> verdict is on the PR (body.comment_posted)
// body.status === 'queued' -> poll the PR for the comment when it lands

← Back to the quickstart · Verdict usage guide · Setup guide · Troubleshooting