Metadata-Version: 2.4
Name: liveapisec
Version: 0.1.39
Summary: LiveAPISec Developer API client — push API specs, run security scans and gate your CI/CD from the command line.
Author: LiveAPISec
License: MIT
Project-URL: Homepage, https://liveapisec.com
Project-URL: Documentation, https://liveapisec.com/settings
Keywords: security,api,dast,scanning,ci,cd
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"

# liveapisec — CLI/SDK for the LiveAPISec Developer API

Official, thin client for the **LiveAPISec Developer API**. Install it once,
use it in any project, script and CI/CD pipeline — no dashboard, no curl.

> **When to use this?** Instead of walking through the wizard in the dashboard,
> a developer pushes endpoints + an optional token from **their own**
> environment (CI/CD, agent, script). The token is generated on your side and
> encrypted server-side (AES-256).
> **Tip: no token = we only test what's public.**

---

## Which APIs does it work with? (not Python-only)

The `liveapisec` CLI is **written in Python** — but that is only the tool you
run. You use it to push, test and monitor APIs built in **any** language and
framework: Python, Node.js, Go, Rust, Java/Kotlin, PHP, Ruby, .NET/C#… It does
not matter how your backend is implemented, as long as it exposes HTTP(S)
endpoints.

**3 ways to get your endpoints in:**

1. **`push`** — list endpoints yourself (works for **any** HTTP API).
2. **`push --openapi-url …`** — pull an OpenAPI spec (FastAPI/DRF, Springdoc,
   NestJS Swagger, express swagger-ui, ASP.NET Swashbuckle…).
3. **`scan-code`** — scan the source code; it auto-detects these
   **11 frameworks**:

| Language | Frameworks recognized by `scan-code` |
|----------|---------------------------------------|
| Python   | FastAPI · Flask · Django              |
| JS/TS    | Next.js (App + Pages Router) · NestJS · Express |
| PHP      | Laravel · PHP/Slim · Lumen            |
| Java     | Spring MVC / Spring Boot (`@GetMapping`) |
| Go       | Gin · Echo · Fiber · Chi · gorilla/mux · `net/http` |
| Rust     | axum · actix-web · rocket · warp      |

**What kind of applications work?** REST/JSON APIs — microservices, monoliths,
BFFs, API gateways, third-party APIs… public or protected (jwt / bearer /
cookie / api_key / OAuth2). `scan-code` reads the HTTP routes; the app behind
them can be anything.

**API types we can test:** REST (OpenAPI / Swagger), **RAML**, **GraphQL**
(introspection or SDL) and **SOAP** (WSDL) — the scanner converts each into
real HTTP targets. **WebSocket** and **gRPC** are not covered by the HTTP
scanner (non-HTTP protocols).

**Integration & SDK:** the package also ships a **Python SDK**
(`from liveapisec import LiveAPISec`), and the same Developer API is a plain
**REST** API you can call from **any language** (curl, Node `fetch`, Go…) — the
CLI just wraps those endpoints. See the [SDK section](#sdk) below and the
in-browser docs at **https://liveapisec.com/docs**.

---

## Installation

### One command (Linux / macOS) — recommended

```bash
curl -fsSL https://raw.githubusercontent.com/LiveApiSec/liveapisec/main/install.sh | bash
```

The installer uses `pipx` when available, otherwise it creates an isolated
virtualenv and symlinks the command into `~/.local/bin` — no `sudo`, and it
works on PEP 668 systems (Ubuntu 24.04+) where a plain `pip install` is
blocked. After installing, open a new terminal and run `liveapisec --help`.

### From PyPI (recommended for developers with pipx/venv)

```bash
pipx install liveapisec     # or: pip install liveapisec (inside a venv)
```

### From GitHub (if you prefer building from the repository)

```bash
pip install "liveapisec @ git+https://github.com/LiveApiSec/liveapisec.git"
```

Verify:

```bash
liveapisec --version     # e.g. liveapisec 0.1.39
liveapisec --help
```

Install once (e.g. in a CI image, on a dev machine, in GitHub Actions) and the
`liveapisec` command is available **in every project** on that machine.

---

## Configuration

Generate an API key once in the dashboard: **Settings → Developer API → Create API key**
(the `las_dev_...` key is shown only once — store it as a secret).

### First run (interactive)

The first time you run a command that needs the API (e.g. `push`, `scan`), the CLI
asks for your key, shows you exactly where to find it, and **saves it** to
`~/.config/liveapisec/config.json` (mode `0600`). Next runs pick it up automatically:

```
$ liveapisec push --name my-api --base-url https://api.example.com ...
No LiveAPISec API key found.
Generate one in the dashboard:  Settings → Developer API → Create API key
  https://liveapisec.com/settings
The key looks like:  las_dev_...
Tip: no key = only public endpoints can be tested.
Paste your API key: las_dev_...
✓ API key saved to /home/you/.config/liveapisec/config.json
```

### Environment variables (recommended for CI)

```bash
export LIVEAPISEC_API_KEY=las_dev_...          # required
export LIVEAPISEC_API_URL=https://api.liveapisec.com   # optional (default; dashboard is liveapisec.com)
```

Precedence: `--api-key` / `--api-url` flags → environment variables →
saved config file.

### Manage the saved key

```bash
liveapisec config        # show where the key is stored
liveapisec config --clear  # remove the saved config file
```

---

## Commands

### Interactive mode (project picker)

When you run `push` / `scan-code` in a terminal and **omit `--project`**, the CLI
shows the projects available for your API key and lets you pick one — or create a
new one:

```
$ liveapisec push --endpoint "GET /users"
No --project given. Pick a project (or create a new one):
  1) api-a  https://a.example.com
  2) api-b  https://b.example.com
  3) create new project
Enter number: 2
→ updating existing project api-b
✓ project 65f...: api-b — 2 endpoints, auth=none
  export PROJECT_ID=65f...
```

In CI (no TTY) the flags are required as before — nothing changes in pipelines.

### 1. `push` — push your API (idempotent, safe in CI)

```bash
liveapisec push \
  --name my-api \
  --base-url https://api.example.com \
  --endpoint "GET /users" \
  --endpoint "POST /payments"
```

- The same `name` + `base_url` = **the same project** (update, not a duplicate) —
  you can call push in every build.
- Instead of a list of endpoints you can provide an OpenAPI spec: `--openapi-url https://api.example.com/openapi.json`.
- Local spec file (no server-side fetch, works with localhost/private URLs that `--openapi-url` blocks): `--spec-file ./openapi.json` (JSON/YAML, parsed locally). The **full** spec is uploaded — parameters, request bodies, schemas and `security` are preserved (so the scanner tests them), not just method+path.
- Optional token: `--auth-type jwt --auth-token <TOKEN>` (or `bearer`,
  `cookie --auth-cookie "session=..."`, `api_key --auth-header X-API-Key`).
  For short-lived JWT login flows, use `--auth-type login` (see below).
- Per-URL automation: `--schedule 6h|12h|24h|weekly` (capped by your plan) and
  `--access external|internal`. `internal` (dev/localhost/private) is **never
  auto-tested** by the scheduler — run it on demand via CLI; setting a schedule
  on an internal URL is rejected.

#### OAuth2 Client Credentials (M2M) — recommended for CI/CD

Short-lived JWTs expire before the scan runs. Instead, register a
**Machine-to-Machine** application in your identity provider (Auth0, Okta,
Azure AD, Keycloak…) once and push the long-lived client credentials — our
scanner fetches a **fresh token at every scan**:

```bash
liveapisec push --name my-api --base-url https://api.example.com \
  --auth-type oauth2 \
  --auth-token-url https://<your-idp>/oauth/token \
  --auth-client-id "$CLIENT_ID" --auth-client-secret "$CLIENT_SECRET" \
  --endpoint "GET /users"
```

#### Login (username/password) — short-lived JWTs

Many APIs issue a short-lived JWT at a **login endpoint**. Instead of pasting a
token that expires, give the login URL + credentials — the scanner **logs in at
every scan** and uses the returned token:

```bash
liveapisec push --name my-api --base-url https://api.example.com \
  --auth-type login \
  --auth-login-url https://api.example.com/auth/login \
  --auth-username qa@example.com --auth-password "$PASSWORD" \
  --endpoint "GET /me"
# optional: --auth-token-field data.access_token  (dot path, default access_token)
#           --auth-username-field email  --auth-password-field password
#           --auth-body json|form
```

#### Clerk (or any JWT-login) — mint a fresh token per scan

If your API is protected by **Clerk** (or another short-lived-JWT provider with a
backend/machine API), give the scanner a **backend secret + a test user** — it
mints a fresh session JWT at every scan (no expired-token problem):

```bash
liveapisec push --name my-api --base-url https://api.example.com \
  --auth-type clerk \
  --auth-clerk-secret "$CLERK_TEST_SECRET" \
  --auth-clerk-user user_xxx \
  --auth-clerk-org org_xxx \
  --endpoint "GET /me"
```

For **cross-tenant / IDOR** testing add a second test user (another org):

```bash
liveapisec hacker --project PROJECT_ID --env dev --thorough \
  --auth-type-b clerk --auth-clerk-secret "$CLERK_TEST_SECRET" \
  --auth-clerk-user user_BBBB --auth-clerk-org org_BBBB
```

> Use a **test** Clerk instance (never production). The secret is stored
> AES-encrypted and never reaches the AI. Tokens are minted via
> `POST /v1/sessions` → `POST /v1/sessions/{id}/tokens`.

#### Verify the token before you commit to it
`--verify` probes the first endpoint with the pushed auth and reports whether
the token actually works (exit 2 on a bad/expired token):

```bash
liveapisec push --name my-api --base-url https://api.example.com \
  --auth-type bearer --auth-token "$TOKEN" \
  --endpoint "GET /users" --verify
# → verify: GET https://api.example.com/users → 200 ✓
#   or: verify: GET https://api.example.com/users → 401 ✗ auth failed — ...
```

> Network errors from `--verify` are informational — your machine may not reach
> the API while our scanner can; what matters is the auth result (2xx vs 401/403).

Output:

```
project 65f...abc: my-api — 2 endpoints, auth=none
export PROJECT_ID=65f...abc
```

### 2. `scan-code` — scan your source code and push the endpoints

Point the CLI at a repo/folder and it detects the framework, extracts the API
endpoints from the code and pushes them — no running project or OpenAPI spec needed.
**The scan runs 100% locally: your code never leaves the machine.** Only the
extracted endpoint list (`METHOD + path`) is sent to the API. (The old name
`push-code` still works as an alias.)

```bash
cd my-project
liveapisec scan-code --dir . --name my-api --base-url https://api.example.com
```

- Auto-detected frameworks: **FastAPI**, **Flask**, **Django**, **Next.js**
  (`app/api` + `pages/api`), **NestJS** (`@Controller`/`@Get`), **Express**
  (`app.get`), **Laravel**, generic **PHP** (`$app->get`, Slim, Lumen),
  **Spring** (`@GetMapping`, Java), **Go** (Gin, Echo, Fiber, Chi, gorilla/mux,
  `net/http`) and **Rust** (axum, actix-web, rocket, warp).
- Scan a git repository straight from a URL (https / ssh / local path) —
  it is shallow-cloned to a temp dir and cleaned up afterwards:

```bash
liveapisec scan-code --repo git@github.com:acme/my-api.git \
  --name my-api --base-url https://api.example.com
```

- Preview before pushing (no API key needed):

```bash
liveapisec scan-code --dir . --name my-api --base-url https://api.example.com --dry-run
liveapisec scan-code --dir . --name my-api --base-url https://api.example.com --dry-run --json
```

- Force a framework if auto-detection misses it: `--framework nextjs`.

Output:

```
framework: fastapi (42 files scanned)
found 58 endpoints:
  GET     /users
  POST    /payments
project 65f...abc: my-api — 58 endpoints, auth=none
export PROJECT_ID=65f...abc
```

> **Note on methods**: FastAPI/Flask/Express/NestJS/Spring/Laravel/Go/Rust carry
> the HTTP method in the code. Django `urlpatterns` and Go `net/http` handlers
> do not — those routes are assumed to be `GET`.

### 3. `scan` — run a security test

```bash
# fire and forget (202, does not wait)
liveapisec scan --project PROJECT_ID --branch main --commit "$GITHUB_SHA"

# wait for the result and fail the build on high (CI gate)
liveapisec scan --project PROJECT_ID --branch main --commit "$SHA" \
  --wait --fail-on high
```

- `--wait` — polls until the scan finishes (default timeout 600 s,
  interval 3 s; change with `--timeout` / `--poll-interval`). When it finishes,
the CLI prints a short **summary** — risk level, coverage (`tested` / `not
deployed`) and **points to improve** (each finding with a one-line fix). Full
descriptive report: `liveapisec report --format md`.
- `--url <name>` — test the project's endpoints against a specific URL (a project can
  have several URLs — dev/staging/prod — all sharing the same endpoints; see
  `liveapisec project --project PROJECT_ID` or `liveapisec urls --project PROJECT_ID`). Each
  URL can pin a spec **version** (`liveapisec urls set --name prod --version X`).
  Without `--url`, the project's default `base_url` is used.
- `--fail-on high` — **exit code 1** when a finding of severity `high`/`critical`
  is found; `--fail-on critical` only for criticals; omit it → always exit 0
  (except errors).

### 4. `hacker` — autonomous AI hacker-mode test (destructive — dev/staging only)

Runs the autonomous AI agent ("real human test") against a **dev/staging**
environment: the LLM plans an attack, probes endpoints step by step (IDOR/BOLA,
broken auth, injections, secrets, mass assignment), can **write and run its own
probe code in a sandbox**, self-corrects, and writes a final evaluation. You watch
the agent think live in the dashboard.

```bash
# dev/staging only — NEVER production (it can break/destroy a system)
liveapisec hacker --project PROJECT_ID --env development
liveapisec hacker --project PROJECT_ID --env staging --wait

# guided mode — give the agent a specific objective (TODO 3.6.2)
liveapisec hacker --project PROJECT_ID --env development \
  --goal "check /users for IDOR — your record vs another user's"

# two identities — differential IDOR/RBAC (A vs B): the agent can send as a/b/anon
liveapisec hacker --project PROJECT_ID --env development --wait \
  --goal "find IDOR" --auth-type-b bearer --auth-token-b "$USER_B_JWT"

# destructive mode (default is READ-ONLY) — allows POST/PUT/PATCH/DELETE
liveapisec hacker --project PROJECT_ID --env development --wait --destructive

# localhost / internal target — through a connected CLI tunnel (terminal 1:
# `liveapisec connect --project PROJECT_ID`, keep running)
liveapisec hacker --project PROJECT_ID --env development --wait --tunnel
```

- `--env` — environment name defined on the project (e.g. `development`, `staging`).
  **Production environments are rejected (403).**
- `--goal` — optional guided attack objective (e.g. "check /users for IDOR",
  "try to escalate to admin", "enumerate secrets"). Without it the agent explores
  freely.
- `--destructive` — **default is READ-ONLY** (only `GET/HEAD/OPTIONS`); write
  methods are blocked by the server. Add `--destructive` to allow state-changing
  methods (mass assignment, writes) on dev/staging.
- `--thorough` — **"real hacker" mode**: no step/request limits (ignores the AI
  cost budget), ALL endpoints in context (not just top-12), a **deterministic
  recon pass** (harvests object IDs from responses + an A/B/anon differential
  matrix → auto-IDOR) and maximal persistence (it does not stop at the first
  401/403/404). Use with `--auth-token-b` for cross-identity tests.
- `--auth-token-b` / `--auth-type-b` — a **second identity**; the agent can send
  the same request as `a`, `b` and `anon` and compare — a confirmed IDOR is when
  `b`/`anon` receives `a`'s data. Needs the project's main credential (slot A) too.
- `--wait` — polls until the AI agent finishes. When it finishes, the CLI
  prints a short **summary**: risk level, requests/steps, the attack plan
  (with revisions), the agent's **recommendations**, and the request/step budget.
- **Domain verification**: public targets need a verified domain (the dashboard
  Domains flow). **Localhost / private IPs (e.g. `http://localhost:8000`, `10.x`)
  are exempt** — no domain verification needed for your own local server.
- Your API URL and credentials are **never** sent to the AI — only relative paths
  reach the model; requests are executed server-side in a sandbox. If the project has
  credentials, the agent runs **authenticated** (it only learns a flag, not the
  token) and can test IDOR/BOLA and privilege escalation as a real user.

### 5. `status` — project status + recent scans

```bash
liveapisec status --project PROJECT_ID
```

### 6. `findings` — scan results

```bash
liveapisec findings --project PROJECT_ID --scan SCAN_ID
liveapisec findings --project PROJECT_ID --scan SCAN_ID --json   # raw data (for agents/AI)
```

### 7. `verdict` — CI regression gate (new/fixed vs baseline)

Compares the scan against a baseline (e.g. last green deploy): `new` findings
are regressions, `fixed` disappeared, `persisting` were already known. Exits 1
when NEW findings reach `--fail-on` (default: high) — the real fix-and-rescan gate:

```bash
liveapisec verdict --project PROJECT_ID --scan NEW_SCAN --baseline BASE_SCAN --fail-on high
```

### 8. `compliance` — PCI DSS / SOC 2 / ISO 27001 / GDPR / NIS2 (SaaS+)

Illustrative mapping of open findings onto framework requirements (with
disclaimer — not a certification):

```bash
liveapisec compliance --project PROJECT_ID --scan SCAN_ID
```

### 9. `report` — full saved scan report (summary + points to improve)

```bash
liveapisec report --project PROJECT_ID --scan SCAN_ID -o report.json   # save to file
liveapisec report --project PROJECT_ID --scan SCAN_ID --json           # print to stdout
liveapisec report --project PROJECT_ID --scan SCAN_ID --format md -o report.md
```

The **Markdown** report (`--format md` / `-o *.md`) is the descriptive post-test
summary:

- scan summary + **coverage** (`tested` / `not deployed on this URL`),
- **Summary** — risk level + how many points to improve,
- **Points to improve** — prioritized, each with **Why** (from the scan) and
  **Fix** (how to remediate); failed questionnaire answers are included too with
  their fix,
- findings table + compliance mapping + `ask` section.

### 10. `ask` — answer what the scanner cannot see (SEC-ASK-N, SaaS+)

Black-box tests stop at the HTTP boundary. `ask` opens a question session:
270 checkable questions (SEC-ASK-1 … SEC-ASK-270: auth, RBAC, tenant
isolation, crypto, business logic, SDLC…) plus AI-tailored extras about YOUR
endpoints. The first AI pass may also add **clarifications** (no priority) about
your system — role names, tenant model, internal endpoints; answer them, then run
`ask followup` and the next round turns those facts into precise questions — roles, org layers, edge cases. You (or your LLM) answer
`pass` / `fail` / `na` by **reading the source code**, each with the fix and
an evidence note. Every question carries a **priority** (critical/high/medium/low) — from the bank's category+pattern rules and, for AI questions, judged by the model; failures are sorted critical-first in the CLI, the panel and the Markdown report. Failures land in the Markdown report next to the findings:

```bash
liveapisec ask new --project PROJECT_ID                 # fresh session (270 + AI)
liveapisec ask sessions --project PROJECT_ID            # pass/fail counts per session
liveapisec ask answer --session SES --question SEC-ASK-5 --verdict fail --note "no MFA in auth.py"
liveapisec ask run --session SES                  # interactive walkthrough
liveapisec ask answer --session SES --question SEC-ASK-CL-1 --verdict info --note "roles: owner/admin/developer/viewer"   # answer a clarification
liveapisec ask followup --session SES             # AI adds deeper questions from your answers
liveapisec ask followup --session SES --rounds 3   # several AI passes (deduped)
liveapisec ask followup --session SES --until-dry  # repeat until a pass adds nothing (cap 5)
liveapisec ask show --session SES --only failed   # review failures
```

### 10. `certificate --pdf` — download the certificate (passed scans only)

```bash
liveapisec certificate --project PROJECT_ID --scan SCAN_ID --pdf --variant full -o cert.pdf
liveapisec certificate --project PROJECT_ID --scan SCAN_ID --pdf --variant client -o cert-client.pdf
```

### 0. `all` — full pipeline in one command (scan → verdict → compliance → report → PDF)

Numbered 0 because it's the easiest path: queues a scan, waits, compares
against the baseline (explicit `--baseline` or auto = previous completed scan),
prints the verdict, saves the report + certificate PDF, and exits 1 on
regressions. Compliance below plan and a missing certificate (scan didn't pass)
are notes, not errors:

```bash
liveapisec all --project PROJECT_ID
liveapisec all --project PROJECT_ID --baseline BASE_SCAN --fail-on high --variant client
liveapisec all --project PROJECT_ID --hacker --env development   # hacker-mode instead (destructive — dev/staging only)
```

### Auth-matrix RBAC test — two identities, no source code needed

Role checks (user role vs organization role, cross-tenant isolation) can't be
tested with one token. Pass a **second identity** and the scanner diffs every
endpoint as anonymous / A / B: secured endpoints reachable anonymously, A
allowed where B is blocked (inconsistent tiers), confirmed BOLA (A reads two
different objects), and admin paths exposed to A:

```bash
# CI: two tokens from secrets (e.g. a low-priv user + an admin)
liveapisec scan --project PROJECT_ID --wait \
  --auth-token-b "$USER_B_JWT" --auth-type-b bearer
liveapisec all --project PROJECT_ID --auth-token-b "$USER_B_JWT"
```

- Identity **A** = the scan's normal auth (saved credential in
  Settings → Credentials, or `--auth-type/--auth-token` on push).
- Identity **B** = `--auth-token-b` (transient: encrypted server-side, lives
  only on this scan) or a saved credential with slot **B** (panel/CI reuse).
- Findings land in category `rbac` (high/medium) and flow into
  verdict → report (md) → compliance → PDF like everything else.

### 11. `project` — project details

Shows the project's endpoints count, default base_url, schedule — and the
list of **URLs** (all tested with the same endpoints). Each URL can test a pinned
spec **version** (`latest` or a snapshot). Run a scan against one with
`scan --url <name>`.

```bash
liveapisec project --project PROJECT_ID
# project 65f...: my-api — 12 endpoints
#   base_url: https://api.example.com
#   urls (same endpoints tested against each):
#     - development: https://api.dev.example.com  [schedule=6h]
#     - staging: https://api.stage.example.com
#     - production: https://api.example.com  [version=1.0.3]
```

### 15. `urls` — manage URLs (environments) of a project

One endpoint set, many addresses (dev/staging/prod). Each URL has its own spec
**version** (`latest` or a snapshot), **schedule** and `paused`. See
[URLs, versions & certificate](../docs/URLS_VERSIONS_CERTIFICATES.md).

```bash
# list
liveapisec urls --project PROJECT_ID

# add (same endpoints tested against each URL)
liveapisec urls add --project PROJECT_ID --name dev  --url https://dev.example.com
liveapisec urls add --project PROJECT_ID --name prod --url https://api.example.com

# pin a spec version / set schedule / pause
liveapisec urls set --project PROJECT_ID --name prod --version 1.0.3
liveapisec urls set --project PROJECT_ID --name prod --schedule 24h
liveapisec urls set --project PROJECT_ID --name prod --paused

# remove
liveapisec urls rm --project PROJECT_ID --name dev
```

`--version` accepts `latest` (default) or a version from
`liveapisec project`/the Versions tab. Adding/updating with an unknown version is
rejected (400) — no silent testing of the wrong spec.

### 16. `versions` — list spec versions of a project

Shows every snapshot of the endpoint set (newest first): version, endpoints
count, date, change note, which URLs pin it (`used by`) and which is `current`.
Use a version with `urls set --version`.

```bash
liveapisec versions --project PROJECT_ID
# versions of project 65f... (newest first):
#   1.0.3 (current)  42 endpoints  2026-09-21  merge: +3 endpoints  used by: dev
#   1.0.2            40 endpoints  2026-09-18  edited GET /users   used by: prod
#   1.0.0            38 endpoints  2026-09-10  initial push (CI/CD)

liveapisec versions --project PROJECT_ID --json   # raw list (for scripts / agents)
```

### 17. `delete` — delete a project

Removes the project and **all** its data (scans, findings, URLs, versions,
credentials, certificate). Asks for confirmation unless `--yes`.

```bash
liveapisec delete --project PROJECT_ID
liveapisec delete --project acme --yes
```

### 12. `scans` — full test (scan) history for a project

See every security test ever run on a project (status, branch/commit, tests run,
findings by severity) — useful for an agent that wants to know what was tested,
when, and with what result:

```bash
liveapisec scans --project PROJECT_ID
# scan 65f...001  status=completed  branch=main  commit=abc  tests=42  findings=3 (high=1 medium=2)
# scan 65f...002  status=failed     branch=main

liveapisec scans --project PROJECT_ID --json        # raw list (for scripts / agents)
liveapisec scans --project PROJECT_ID --limit 5     # only the 5 most recent
```

### 13. `projects` — last test status per project (no dashboard needed)

See every project and the **last security test result** straight in the
terminal — no need to open the dashboard:

```
$ liveapisec projects
svc
  api-a  https://a.example.com  last test: completed · 42 tests · 3 findings (high=1 medium=2)
  api-b  https://b.example.com  last test: failed
mobile
  api-c  https://c.example.com  last test: no test yet

# JSON (for scripts / agents)
liveapisec projects --json

# Only one project
liveapisec projects --project svc
```

### 9. `certificate` — live certificate URL + embed snippet

After a scan is green, publish the live certificate. Choose the scope: whole
organisation (default), one project, or a single URL.

```bash
liveapisec certificate                                  # whole organisation
liveapisec certificate --scope project --project acme
liveapisec certificate --scope project --project PROJECT_ID
liveapisec certificate --type badge   # badge | banner | card | iframe

# which URL the PUBLIC project certificate concerns (not shown publicly):
liveapisec certificate --project PROJECT_ID --url production
liveapisec certificate --project PROJECT_ID --url ""          # back to the default base_url
```

Paste the returned snippet (an `<a href=...>` wrapping `<div data-liveapisec-widget ...>`,
plus `widget.js`) into your project, docs or trust page. The link is static in the HTML
(crawlable) and the whole widget is clickable — it updates with every scan.

The public certificate can be generated for any URL, but the **public** project
certificate reflects one selected URL (its status is computed only from that
URL's scans). The URL itself is **never shown** on the public page/widget — see
[URLs, versions & certificate](../docs/URLS_VERSIONS_CERTIFICATES.md).

### 14. `connect` — reverse tunnel (test localhost / internal)

The scan runs on LiveAPISec's scanner, so it normally cannot reach a target that
exists only on your machine. Start a tunnel — the CLI then acts as a proxy:

```bash
# terminal 1 — keep running
liveapisec connect --project PROJECT_ID

# terminal 2 — route the scan through the CLI
liveapisec scan --project PROJECT_ID --wait --tunnel

# hacker-mode works through the tunnel too (dev/staging only)
liveapisec hacker --project PROJECT_ID --env development --wait --tunnel
```

Only the project's `base_url` host is forwarded (not an open proxy).

---

> **Full documentation:** see the in-browser docs at **https://liveapisec.com/docs**
> (install, config, every command, auth/OAuth2, exit codes, GitHub Actions, SDK).

---

## GitHub Actions — full example (gate on push)

```yaml
name: liveapisec
on: push
jobs:
  security-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - name: Install CLI
        run: pip install "liveapisec @ git+https://github.com/LiveApiSec/liveapisec.git"
      - name: Full pipeline — scan, regression gate, report, certificate
        id: sectest
        env:
          LIVEAPISEC_API_KEY: ${{ secrets.LIVEAPISEC_KEY }}
        run: |
          liveapisec push --name my-api --base-url "$BASE_URL" \
            --endpoint "GET /users" --endpoint "POST /payments"
          liveapisec all --project "$PROJECT_ID" --fail-on high \
            --format md --report-out security-report.md
      - name: Upload security report + certificate
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: liveapisec-report
          path: |
            security-report.md
            liveapisec-certificate-*.pdf
```

**How a failure shows up in CI:** `all` exits **1** when NEW findings reach
`--fail-on` (compared against the baseline = previous completed scan), so the
`Full pipeline` step turns red and the workflow fails — exactly like a failing
test suite. The Markdown report (`security-report.md`) is attached as an
artifact either way (`if: always()`), so reviewers see which findings are new:
open the run → *Artifacts* → `liveapisec-report`. The same exit-code contract
works in GitLab CI, Jenkins or plain bash (`set -e` stops the pipeline on
regressions). For agents/AI parsing, use `--json` on `verdict`/`scan` instead.

> **Why is push safe?** Push is idempotent (name+base_url → the same project), so
> the next build does not create junk — it updates endpoints and the token, and
> the next `scan` tests the latest state.

---

## Exit codes

| Code | Meaning |
|------|---------|
| 0    | OK (`verdict: pass` — no NEW findings at/above the threshold) |
| 1    | Gate failed — NEW findings at/above `--fail-on` vs baseline (`verdict`/`all`) |
| 2    | Usage error / API error / scan did not complete |

---

## Development / tests

```bash
pip install -e ./cli[dev]
cd cli && python -m pytest tests/ -q
```

## SDK (API)

### Python

Besides the CLI, the package also exports a client for scripts:

```python
from liveapisec import LiveAPISec

api = LiveAPISec()  # LIVEAPISEC_API_KEY from env
project = api.create_project("my-api", "https://api.example.com",
                       endpoints=[{"method": "GET", "path": "/users"}])
scan = api.trigger_scan(project["project_id"], branch="main", commit="abc")
done = api.wait_for_scan(project["project_id"], scan["scan_id"])
blocked = LiveAPISec.findings_above(done["findings"], "high")
```

### From any other language — plain REST

Not on Python? The endpoints behind the CLI are a standard REST API. Call them
with **curl**, Node `fetch`, Go, Java, Ruby… anything that speaks HTTP — you get
the same result as the CLI (endpoints pushed, scans run, findings readable by
your agent or CI):

```bash
# push a project + endpoints from any language
curl -X POST $LIVEAPISEC_API_URL/developers/projects \
  -H "Authorization: Bearer $LIVEAPISEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-api","base_url":"https://api.example.com",
       "endpoints":[{"method":"GET","path":"/users"}]}'

# trigger a scan
curl -X POST $LIVEAPISEC_API_URL/developers/projects/$PROJECT_ID/scans \
  -H "Authorization: Bearer $LIVEAPISEC_API_KEY" \
  -d '{"branch":"main","commit":"abc123"}'
```

---

## Instructions for AI coding assistants (Copilot, Cursor, etc.)

Copy-paste the block below into your AI assistant (GitHub Copilot, Cursor,
Windsurf…) to let it use the LiveAPISec CLI on your behalf.

````markdown
You can use the `liveapisec` CLI (https://pypi.org/project/liveapisec/) to
push API specifications, run security scans and read results against the
LiveAPISec platform. It works with APIs in ANY language/framework (not just
Python) — use `scan-code` to extract endpoints from source, or
`push --openapi-url` for an OpenAPI spec.

Environment:
- `LIVEAPISEC_API_KEY` — dev API key (las_dev_...), usually already set.
- `LIVEAPISEC_API_URL` — API base URL (default: https://api.liveapisec.com).
- `PROJECT_ID` — the project id returned by `liveapisec push` / `liveapisec project`.

Fetch information about the portal (to diagnose and fix issues):
- `liveapisec projects`              — every project + last test status
- `liveapisec scans --project <id>`    — full test history for a project (--json for raw)
- `liveapisec status --project <id>`   — project status + recent scans
- `liveapisec project --project <id>`    — project details
- `liveapisec findings --project <id> --scan <scan_id>`  — findings (--json for raw)

Language note: the CLI is written in Python, but it tests APIs built in ANY
language/framework — Python, Node.js, Go, Rust, Java, PHP, Ruby, .NET, etc.
Use `liveapisec scan-code` to auto-extract endpoints from the source
(FastAPI, Flask, Django, Next.js, NestJS, Express, Laravel, PHP/Slim, Spring,
Go, Rust), or `push --openapi-url` for any API that exposes an OpenAPI spec.

Workflow:
1. Push the API under test (idempotent — safe to repeat):
   `liveapisec push --name <name> --base-url <url> --endpoint "METHOD /path" [--endpoint ...] [--openapi-url <url>] [--auth-type jwt|bearer|cookie|api_key --auth-token <token>]`
2. Run a security scan and wait for the result:
   `liveapisec scan --project <project_id> --branch <branch> --commit <sha> --wait`
3. Read findings (severity, title, target):
   `liveapisec findings --project <project_id> --scan <scan_id>` (add `--json` for raw JSON).

Self-repair loop (fix an issue from our test, end-to-end):
1. See what failed: `liveapisec projects`
2. Find the failed scan: `liveapisec scans --project <project_id>`
3. Read the findings: `liveapisec findings --project <project_id> --scan <scan_id> --json`
4. Fix the code (e.g. add a security-header middleware), commit.
5. Re-push (idempotent) and re-run the gate:
   `liveapisec push --name <name> --base-url <url> --endpoint "GET /x"`
   `liveapisec scan --project <project_id> --branch <branch> --commit <sha> --wait --fail-on high`
6. Confirm the gate is green: `liveapisec projects`

Rules:
- Never print or commit the API key; use the environment variable.
- If a scan fails, read the findings, fix the code, re-push and re-scan.
- Push is idempotent, so re-running it is always safe.
- Exit code 1 from `scan --wait --fail-on <sev>` means the gate failed
  (findings at/above that severity); exit 2 means usage/API error.
````

### Auth profile (auto-detected on scan)

After every scan the CLI/API **detect and store** which endpoint groups require
authentication (universal: from observed `401/403` on GET/HEAD probes **and** the
OpenAPI per-operation `security` — works even if the spec is incomplete). See it
with `liveapisec project --project PROJECT_ID`:

```
  auth schemes (from spec): HTTPBearer(http), DevApiKey(http)
  auth requirements (from last scan):
    - /api-specs: requires auth (25 auth / 0 public of 25)  [HTTPBearer]
    - /developers: requires auth (35 auth / 0 public of 35)  [HTTPBearer, DevApiKey]
    - /public: public (0 auth / 6 public of 6)
  credentials configured: clerk, api_key
```

It also feeds the report as a coverage gap: an endpoint group that requires auth
but has **no matching credential** was tested unauthenticated — so the results
don't cover it. Add the right credential (or a per-identity scan) to test it for
real.

### Credentials per-prefix — mixed auth in one scan

Some APIs expose **different auth mechanisms on different routes** (e.g. user
JWT/Clerk on the core API, but machine `api_key` tokens on `/developers/*`). A
single credential can't authenticate both. Bind a credential to a **path prefix**
and the scanner picks the right one **per request** (longest prefix wins):

```bash
liveapisec credentials set --project PROJECT_ID --slot a \
  --auth-type bearer --auth-token "$CLERK_JWT"          # default (all routes)
liveapisec credentials set --project PROJECT_ID --slot devkey --path /developers \
  --auth-type api_key --auth-token "$DEV_KEY" --auth-header X-API-Key

liveapisec credentials --project PROJECT_ID
#   - slot=a       bearer   prefix=(default)
#   - slot=devkey  api_key  prefix=/developers
liveapisec credentials rm --project PROJECT_ID --slot devkey
```

One `scan` now authenticates **both** groups; the auto **auth profile** confirms
it (both shown as `public`/2xx instead of `requires auth`). If a group still
shows `requires auth`, its credential is missing/wrong.
