Vulnix
API

Billing

Read the plan, credit balance, and ledger history — so automation can check it can afford to 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 can afford a run before starting one, so a pipeline fails with a clear message instead of a 422 credits_exhausted halfway through.

Plans

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

Every plan available, not just yours.

{
  "plans": [
    {
      "id": "…",
      "name": "…",
      "monthly_price_usd": 0,
      "monthly_credits": 0,
      "is_self_serve": true
    }
  ]
}

Prop

Type

Current status

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

The endpoint to poll before spending. 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 credit_balance_usd unconditionally will throw on them. Check is_exempt first and treat it as "spending is not limited here".

Monthly credits are spent before top-up credits

monthly_balance_usd resets at current_period_end and does not carry over. topup_balance_usd persists until spent. So a healthy-looking credit_balance_usd can still be about to shrink at the period boundary — if you are scheduling work near it, read the two components, not just the total.

Ledger history

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

Every credit movement, for reconciliation and per-team cost attribution.

{
  "history": [
    {
      "id": "01M2NP…",
      "delta_usd": -5.0,
      "balance_after_usd": 95.0,
      "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 ledger into a cost 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 spend credits 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 affordability before a run

BALANCE=$(curl -fsS https://api.vulnix.dev/billing/status \
  -H "Authorization: Bearer $VULNIX_TOKEN" \
  | jq 'if .is_exempt then 1e9 else .credit_balance_usd end')

ESTIMATE=$(curl -fsS "https://api.vulnix.dev/scopes/$SCOPE_ID" \
  -H "Authorization: Bearer $VULNIX_TOKEN" | jq '.estimated_cost_usd')

if (( $(echo "$BALANCE < $ESTIMATE" | bc -l) )); then
  echo "Not enough credits: have $BALANCE, need about $ESTIMATE" >&2
  exit 1
fi

estimated_cost_usd on the scope is an estimate, not the exact charge — leave headroom rather than comparing to the cent.

On this page