CI/CD integration
Trigger a fresh analysis from your build pipeline, pull the numbers as JSON, and use them however you like — gate releases on a regression budget, post a PR comment, ping Slack, feed a dashboard, embed a fresh badge in the docs.
The endpoints you'll use
- POST
/api/analyze— kick off (or short-circuit to cache) an analysis. Body:{"url": "https://github.com/owner/repo"}. Add"force": trueto ignore the cache. - GET
/api/repo/{owner}/{repo}?view=totals— get just the headline numbers. - GET
/api/repo/{owner}/{repo}?view=all— full payload (summary, by-file, totals). - GET
/badge/{owner}/{repo}.svg— live SVG badge for READMEs.
The JSON endpoints return 404 if a repo hasn't been analyzed yet — POST to
/api/analyze first.
Pattern 1 · curl in any pipeline
Works in GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite — anywhere with bash and curl.
# 1. Trigger (or pick up cached) analysis curl -sS -X POST https://repostats.app/api/analyze \ -H 'Content-Type: application/json' \ -d '{"url":"https://github.com/'"$GITHUB_REPOSITORY"'"}' > /dev/null # 2. Poll up to 3 minutes for the result for i in $(seq 1 60); do sleep 3 body=$(curl -sS -w '%{http_code}' "https://repostats.app/api/repo/$GITHUB_REPOSITORY?view=totals") status="${body: -3}"; data="${body:0:${#body}-3}" [ "$status" = "200" ] && break done # 3. Read the metrics with jq echo "$data" | jq -r '"cost=$\(.cocomo_cost_usd|round) loc=\(.code) files=\(.files)"'
Pattern 2 · GitHub Actions workflow
Paste this into .github/workflows/repostats.yml — runs on every push to
main, plus every PR, and writes a job summary with the headline numbers.
name: repostats on: push: { branches: [main] } pull_request: jobs: analyze: runs-on: ubuntu-latest steps: - name: Trigger repostats analysis run: | curl -sS -X POST https://repostats.app/api/analyze \ -H 'Content-Type: application/json' \ -d "{\"url\":\"https://github.com/${{ github.repository }}\",\"force\":true}" - name: Wait for completion + read totals id: read run: | for i in $(seq 1 60); do sleep 3 code=$(curl -sS -o totals.json -w '%{http_code}' \ "https://repostats.app/api/repo/${{ github.repository }}?view=totals") [ "$code" = "200" ] && break done jq -r '"cost=$\(.cocomo_cost_usd|round)\nloc=\(.code)\nfiles=\(.files)"' totals.json >> "$GITHUB_OUTPUT" - name: Write job summary run: | { echo "### 📊 repostats" echo "" echo "| metric | value |" echo "|---|---|" echo "| cost to develop | \$${{ steps.read.outputs.cost }} |" echo "| lines of code | ${{ steps.read.outputs.loc }} |" echo "| files | ${{ steps.read.outputs.files }} |" echo "" echo "[Full report →](https://repostats.app/r/${{ github.repository }})" } >> "$GITHUB_STEP_SUMMARY"
Pattern 3 · fail the build on a regression budget
Compare the current numbers against a checked-in baseline. If LOC or cost jumped more than you allow, exit non-zero.
# prerequisite: .repostats-budget.json checked into the repo
# { "max_code": 100000, "max_cost_usd": 5000000 }
curl -sS "https://repostats.app/api/repo/$GITHUB_REPOSITORY?view=totals" > totals.json
code=$(jq '.code' totals.json)
cost=$(jq -r '.cocomo_cost_usd | floor' totals.json)
max_code=$(jq '.max_code' .repostats-budget.json)
max_cost=$(jq '.max_cost_usd' .repostats-budget.json)
if [ "$code" -gt "$max_code" ] || [ "$cost" -gt "$max_cost" ]; then
echo "::error::repostats budget exceeded — code=$code (max $max_code), cost=$cost (max $max_cost)"
exit 1
fi
Pattern 4 · PR comment with the cost delta
Same as Pattern 2 but also fetches the base-branch SHA's analysis (if it exists) and posts the delta as a sticky PR comment. Sketch:
- name: Comment delta on PR if: github.event_name == 'pull_request' uses: marocchino/sticky-pull-request-comment@v2 with: header: repostats message: | **repostats** — cost +$${{ steps.delta.outputs.cost }} (was \$${{ steps.delta.outputs.cost_before }}) lines +${{ steps.delta.outputs.loc }} (was ${{ steps.delta.outputs.loc_before }}) [Full report →](https://repostats.app/r/${{ github.repository }})
Pattern 5 · live badge in your README
Already covered on the Embed page. TL;DR:
[](https://repostats.app/r/owner/repo)
Auth, rate limits, fair use
GETs aren't rate-limited.
Private repos aren't supported in v1. If your CI hits a lot of repos in one batch,
paginate the work or get in touch and we'll wire up an API key.
Response schemas
GET /api/repo/{owner}/{repo}?view=totals returns:
{
"sha": "d60ec36cab33...",
"default_branch": "master",
"files": 88121,
"lines": 42014443,
"code": 32003044,
"comment": 4740609,
"blank": 5270790,
"complexity": 2658913,
"bytes": 1325419528,
"languages": 51,
"cocomo_effort_months": 65621.06,
"cocomo_schedule_months": 218.77,
"cocomo_people": 589.75,
"cocomo_cost_usd": 1452420856.81,
"clone_ms": 33113,
"scc_ms": 7308
}