Skip to content

CI code review

Every pull request can get an automated review without anyone installing a local tool. A GitHub Actions job captures the PR diff, sends it to POST https://api.mirapi.ai/v1/chat/completions, and posts the model’s findings back as a comment.

Deliverable: every pull request gets an automated review comment from MirAPI.

Prerequisites: a GitHub repository with Actions enabled; a MIRAPI_API_KEY stored as a repository secret.

  1. Create a dedicated key in https://console.mirapi.ai → API Keys.
  2. Set a spend quota on it — CI runs unattended, and the quota caps the damage of a buggy workflow.
  3. In GitHub: Settings → Secrets and variables → Actions, add MIRAPI_API_KEY.

Never put the key in the workflow file or in .env inside the repo. A dedicated CI key also keeps that spend visible in one place for reconciliation.

Create .github/workflows/mirapi-review.yml:

name: MirAPI code review
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Capture the diff
run: |
git fetch origin "${{ github.event.pull_request.base.ref }}"
git diff "origin/${{ github.event.pull_request.base.ref }}...HEAD" > /tmp/pr.diff
wc -c /tmp/pr.diff
- name: Review with MirAPI
env:
MIRAPI_API_KEY: ${{ secrets.MIRAPI_API_KEY }}
run: |
python3 - <<'PY'
import os, sys, requests
diff = open("/tmp/pr.diff", encoding="utf-8", errors="replace").read()[:30000]
resp = requests.post(
"https://api.mirapi.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"},
json={
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "You are a careful code reviewer. Report bugs, security issues, and regressions concisely."},
{"role": "user", "content": f"Review this diff:\n\n{diff}"},
],
"max_tokens": 1500,
},
timeout=120,
)
if resp.status_code != 200:
print(f"MirAPI error {resp.status_code}: {resp.text}", file=sys.stderr)
sys.exit(1)
body = resp.json()["choices"][0]["message"]["content"]
open("/tmp/review.txt", "w").write(body)
PY
- name: Post the comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr comment "${{ github.event.pull_request.number }}" --body-file /tmp/review.txt

The request body has three fields that matter here:

Field Purpose
model The model that writes the review — deepseek-chat in the example.
messages A system prompt with review instructions plus a user message carrying the diff.
max_tokens Caps the review length, which also caps the token spend on each run.

Optional tuning: set reasoning_effort to make a reasoning model think harder (reasoning tokens bill as output), or use response_format to have the model return JSON when it supports structured output.

The same key works across three protocol skins. The example above uses the OpenAI skin; the other two let you fire the same review from Anthropic- or Gemini-style clients.

Protocol Base URL Endpoint Auth header
OpenAI https://api.mirapi.ai/v1 POST /chat/completions Authorization: Bearer
Anthropic https://api.mirapi.ai POST /v1/messages x-api-key or Authorization: Bearer
Gemini https://api.mirapi.ai POST /v1beta/models/{model}:generateContent x-goog-api-key
POST https://api.mirapi.ai/v1/chat/completions
Authorization: Bearer sk-...
{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "You are a careful code reviewer."},
{"role": "user", "content": "Review this diff:\n\n…"}
],
"max_tokens": 1500
}
POST https://api.mirapi.ai/v1/messages
x-api-key: sk-...
{
"model": "deepseek-chat",
"max_tokens": 1500,
"system": "You are a careful code reviewer.",
"messages": [
{"role": "user", "content": "Review this diff:\n\n…"}
]
}
POST https://api.mirapi.ai/v1beta/models/deepseek-chat:generateContent
x-goog-api-key: sk-...
{
"contents": [
{
"role": "user",
"parts": [{"text": "You are a careful code reviewer.\n\nReview this diff:\n\n…"}]
}
]
}

Not every model is reachable through every skin — check GET /v1/models for a model’s capabilities before wiring it into CI.

The workflow should fail loudly rather than post an empty comment, so the script exits non-zero on a non-200 response. The status codes you are most likely to meet in CI:

Code Meaning What to do
401 Missing or invalid key Fix the MIRAPI_API_KEY secret
403 Valid key, request rejected (balance, whitelist, quota, IP) Resolve the cause, then retry
429 Rate limited Back off with jitter (1s → 2s → 4s, capped ~30s) — the gateway does not retry for you
500 Gateway or upstream failure Retry idempotent calls

The error body uses the OpenAI envelope — {"error": {"message", "type", "param", "code"}} — and every message ends with a request ID. Save that ID from a failed run; it is what support needs to look up the call.

Open a pull request and check the review comment appears. Then confirm the request was billed:

Terminal window
curl https://api.mirapi.ai/api/log/token -H "Authorization: Bearer $MIRAPI_API_KEY"

/api/log/token lists per-request rows; https://api.mirapi.ai/api/usage/token gives account-level totals. A non-streaming response only reports token counts in usage — the dollar amount lives in the console and these logs.

  • Rate limits: several commits in one push still produce one review (one synchronize event). If you run several models or longer prompts, pace them; MirAPI returns 429 when you exceed the limit.
  • Diff size: the script truncates at 30 000 characters; split very large diffs across calls if needed.
  • 403 with a valid key means the quota is exhausted or the model is not whitelisted — raise the quota or fix the whitelist.

One review of a typical PR is a few thousand tokens — usually a fraction of a cent to a few cents. Spend draws from your prepaid balance, which never expires, so an idle workflow costs nothing. To keep reviews cheap, lower max_tokens, truncate the diff, and keep the system prompt identical across runs so it can benefit from automatic prompt caching.