Vulnix
API

Billing

Read the plan, pentests left and PR reviews used — so automation can check it can start a run before it tries.

Billing over the API is read-only. There is a billing:read scope and no billing:write, because nothing a token can do should be able to move money.

The reason to reach for these endpoints is the one that matters in CI: checking you have a pentest left before starting one, so a pipeline fails with a clear message instead of a 422 credits_exhausted halfway through. (credits_exhausted is the error code for "no pentests left on the plan", or "the plan is not active".)

Plans

GET /billing/plans · scope billing:read · no minimum role

Every plan available, not just yours.

{
  "plans": [
    {
      "id": "startup",
      "name": "Startup",
      "monthly_price_usd": 99,
      "is_self_serve": true,
      "monthly_pentests": 3,
      "monthly_pr_reviews": 50
    }
  ]
}

Prop

Type

Current status

GET /billing/status · scope billing:read · role viewer

The endpoint to poll before starting a run. Two shapes — check is_exempt first.

An exempt organization returns exactly this and nothing else:

{ "is_exempt": true }

Everyone else:

Prop

Type

`is_exempt: true` means the other fields are absent, not zero

Demo and internal organizations return only { "is_exempt": true }. Code that reads pentests.left unconditionally will throw on them. Check is_exempt first and treat it as "starting a run is not limited here".

A plan's monthly pentests renew at current_period_end and do not carry over, so a healthy-looking pentests.left can still be about to reset at the period boundary.

History

GET /billing/history · scope billing:read · role admin

Every movement of pentests, for reconciliation and per-team attribution.

{
  "history": [
    {
      "id": "01M2NP…",
      "delta_pentests": -1,
      "pentests_after": 2,
      "reason": "run_debit",
      "source_type": "run",
      "source_id": "01M2NP4K8QW3ZR7YT2XVB9CDEF",
      "created_at": "2026-09-16T18:04:11.229104+00:00"
    }
  ]
}

Prop

Type

Because source_id carries the idempotency_key you chose, deriving that key from something meaningful — a deploy id, a service name, a team prefix — turns this history into a breakdown you can group without any extra bookkeeping.

Console-only billing endpoints

Anything that moves money is unreachable with a token, whatever scopes it carries. All return 403 api_token_route_not_allowed:

Prop

Type

Starting a pentest does use one of your pentests and is available with runs:write — that is the automation people actually need, and its cost is bounded and predictable. Changing what you owe every month is not. See what tokens cannot do.

Checking before a run

LEFT=$(curl -fsS https://api.vulnix.dev/billing/status \
  -H "Authorization: Bearer $VULNIX_TOKEN" \
  | jq 'if .is_exempt then 1000000 else .pentests.left end')

if [ "$LEFT" -lt 1 ]; then
  echo "No pentests left on the plan" >&2
  exit 1
fi

On this page