API reference
Patchwire ships an OpenAPI 3 specification. The interactive reference is at:
https://api.patchwire.app/docs
That page is rendered by Redoc against the live /openapi.json endpoint, so it always matches the deployed API exactly.
The raw spec is at:
https://api.patchwire.app/openapi.json
You can pipe that into your favourite client generator — openapi-generator-cli, oapi-codegen for Go, progenitor for Rust, etc.
Authentication
Every endpoint other than /v1/auth/register, /v1/auth/login, and /healthz requires a bearer token:
Authorization: Bearer <jwt>JWTs are issued by POST /v1/auth/register and POST /v1/orgs/{slug}/auth/login. They:
- Last 24 hours by default.
- Carry the org slug and user role in the payload.
- Are scoped to one organisation — calling another org's endpoint with your token returns 403 with
"token is for a different organization".
Common patterns
Resource scoping
Every per-org endpoint is shaped /v1/orgs/{org_slug}/<resource>:
/v1/orgs/{org_slug}/projects
/v1/orgs/{org_slug}/projects/{project_slug}
/v1/orgs/{org_slug}/projects/{project_slug}/scans
/v1/orgs/{org_slug}/projects/{project_slug}/scans/{scan_id}
/v1/orgs/{org_slug}/projects/{project_slug}/scans/{scan_id}/findings
/v1/orgs/{org_slug}/findings
/v1/orgs/{org_slug}/usersThe org slug in the URL must match the slug in your JWT.
Errors
All error responses share one shape:
{
"error": "bad_request",
"message": "slug must be 2–40 chars (got 1)"
}The error field is a stable machine-readable identifier; the message may change wording. Use error for code paths.
Idempotency
POST endpoints are not idempotency-keyed. Retrying a POST /projects with the same slug returns a 409. Retrying a POST /run-scan creates a new scan row.
Quick examples
Register
curl -X POST https://api.patchwire.app/v1/auth/register \
-H 'content-type: application/json' \
-d '{
"org_name": "Acme Inc.",
"org_slug": "acme",
"email": "you@acme.example",
"password": "use-a-real-password-here"
}'Response:
{
"token": "eyJ0eXAiOiJKV1Qi…",
"expires_at": "2026-04-26T09:34:20Z",
"user": { "id": "…", "email": "…", "role": "admin", "status": "active", … }
}Create a project with a private-repo token
curl -X POST https://api.patchwire.app/v1/orgs/acme/projects \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{
"slug": "web",
"name": "Acme web app",
"repo_url": "https://github.com/acme/web.git",
"default_branch": "main",
"access_token": "github_pat_…",
"access_token_user": "x-access-token"
}'Response never includes access_token or access_token_enc — only has_access_token: true.
Trigger a scan manually
curl -X POST https://api.patchwire.app/v1/orgs/acme/projects/web/run-scan \
-H "authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"clone_url":"https://github.com/acme/web.git","branch":"main"}'Response is 202 Accepted with the new scan row. Poll GET …/scans/{id} for status; webhooks for the same project will also write into the scans table.
Rate limits
There are no rate limits on the API today. The webhook endpoints accept whatever the SCM throws at them — Patchwire's bottleneck is scanner throughput, not request rate. A future limit will land before commercial use.