Skip to content

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:

http
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}/users

The org slug in the URL must match the slug in your JWT.

Errors

All error responses share one shape:

json
{
  "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

bash
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:

json
{
  "token": "eyJ0eXAiOiJKV1Qi…",
  "expires_at": "2026-04-26T09:34:20Z",
  "user": { "id": "…", "email": "…", "role": "admin", "status": "active",  }
}

Create a project with a private-repo token

bash
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

bash
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.

Released under a proprietary licence.