Pushpen has one real API endpoint, and it exists to power the official GitHub Action (published on the GitHub Marketplace). This page documents it accurately, verified against the current code — not a general-purpose developer API, and not a roadmap promise.
If you want automatic documentation without touching your own CI, connect a repository from the dashboard instead — that path needs no API key and no workflow file.
POST https://pushpen.dev/api/action/generate-docs
Sent as two separate request headers, not a single Bearer token:
x-pushpen-api-key — an API key generated from the dashboard under Settings → API Keys. Account-wide, not repo-scoped — it identifies which Pushpen account and plan to bill against. Shown once at creation; only its prefix (pk_live_...) is ever visible again.
x-github-token — a GitHub token with write access (contents + pull requests) to the target repository.
The supported way to get the GitHub token is the official Action, where it's the workflow's own automatic GITHUB_TOKEN — passed explicitly as github-token: ${{ secrets.GITHUB_TOKEN }}, no separate setup. This is the only integration path Pushpen documents or supports.
For completeness: the endpoint itself doesn't verify that the token actually came from a GitHub Actions run — any valid token with equivalent write access would be accepted the same way. That isn't a documented or supported integration path; calling this endpoint outside the Action is unverified territory with no guarantees about behavior across future changes.
{
"repoOwner": "octocat", // required
"repoName": "hello-world", // required
"eventName": "push", // optional, defaults to "push"
"headSha": "a1b2c3d...", // optional — used to ignore duplicate deliveries of the same commit
"commits": [ // optional — present for push events; drives what gets generated
{
"id": "a1b2c3d...",
"message": "fix: handle empty responses",
"author": { "name": "Jane Doe", "email": "jane@example.com" },
"added": [], "modified": ["src/api.ts"], "removed": []
}
],
"docTypes": ["readme", "changelog"] // optional — narrows your plan's defaults, never expands past them
}repoOwner and repoName must match ^[a-zA-Z0-9_.-]+$. If a repository named by repoOwner/repoNameisn't already connected to your account, this request connects it automatically (subject to your plan's repository limit, below) and generates an initial documentation baseline from the full repository instead of a diff, since there's nothing to diff against yet.
{
"success": true,
"generated": ["readme", "changelog"],
"prUrl": "https://github.com/octocat/hello-world/pull/47", // present only if a PR was opened
"unchanged": ["api-docs"], // present only if some doc types had nothing to change
"failed": ["onboarding"], // present only if generation itself failed for a doc type
"connected": false, // true only on this repo's first-ever request
"kickstart": false // true only when this run generated the initial baseline
}deliveryFailed and skipped_no_match can also appear, each only when relevant — none of the array fields are guaranteed present. No PR is opened (no prUrl) if the repository has auto-commit enabled and the delivery committed directly instead.
Two edge cases return 200 without the shape above:
{ "message": "duplicate delivery, ignored" }Returned when headSha matches a request already handled recently — redelivery protection, not an error.
{ "skipped": true, "reason": "kickstart_in_progress" }Returned if this repository's first-run baseline is already being generated by a concurrent request.
| Status | error | When |
|---|---|---|
| 401 | missing_api_key | x-pushpen-api-key header not sent |
| 400 | missing_github_token | x-github-token header not sent |
| 401 | invalid_api_key | Key doesn't match any active key, or it's been revoked |
| 429 | rate_limited | Over 50 requests/hour for this specific API key |
| 400 | invalid_request | Request body fails validation (bad repoOwner/repoName format, malformed commits, etc.) |
| 403 | repo_limit_reached | First-ever request for this repo, and your plan's connected-repo limit is already used |
| 500 | connect_failed | Auto-connecting the repository failed at the database level |
| 403 | limit_reached | Your plan's generation quota (doc-generation count) is used up |
| 403 | plan_required | docTypes explicitly requested only paid-plan doc types (api-docs/onboarding) on the free plan |
Every error body is { error, message }, with upgrade_url: "/upgrade" added for plan/limit errors. The limit_reached and plan_required cases also carry connected/kickstart, since they're returned after the repo-connect step already ran.
50 requests per hour, per API key — independent of any other Pushpen rate limit (the dashboard and webhook paths are limited separately).
Free-plan keys can generate readme and changelog only — requesting api-docs or onboarding is silently dropped (or rejected with plan_required if that leaves nothing else requested), never expanded past what the plan actually allows. Starter, Pro, Teams plans can generate all 4 doc types. Repository-count and generation-quota limits are identical to the dashboard and webhook paths — this endpoint enforces the same limits, not a separate copy of them. Current plan details: pushpen.dev/pricing.
See the pushpen-action README for the full workflow YAML example and required permissions: block. A machine-readable summary of Pushpen as a whole, including this endpoint's role, is at /llms-full.txt.