diff --git a/api-reference/authentication.mdx b/api-reference/authentication.mdx
new file mode 100644
index 0000000..5f86117
--- /dev/null
+++ b/api-reference/authentication.mdx
@@ -0,0 +1,96 @@
+---
+title: "Authentication"
+description: "Create API keys and authenticate requests to the Hacktron REST API."
+---
+
+The Hacktron REST API authenticates every request with an organization‑scoped API key sent in the `X-Api-Key` header.
+
+API keys are:
+
+- **Organization‑scoped** — each key belongs to exactly one organization. You never need to pass an organization ID alongside the key.
+- **Scoped** — each key declares one or more scopes (`read`, `write`, `delete`) that gate which endpoints it can call.
+
+## Creating an API key
+
+API keys are created from the Hacktron dashboard. You must have the **Admin** or **Owner** role in the organization you are creating a key for.
+
+1. Sign in to [app.hacktron.ai](https://app.hacktron.ai) and switch to the organization you want the key to belong to.
+2. Open **Settings → API keys**.
+3. Click **Create API key**.
+4. Give the key a descriptive name (for example `ci-pipeline`, `backstage-integration`).
+5. Choose the scopes the key needs — pick the minimum set your integration requires. See [Scopes](#scopes).
+6. Optionally set an expiration date.
+7. Click **Create**.
+
+
+ The full API key is shown **only once**, immediately after creation. Copy it and store it in a secret manager right away. If you lose it, you will need to revoke the key and create a new one.
+
+
+Each organization can have at most **10 active API keys** at a time. Revoke unused keys before creating new ones if you hit the limit.
+
+### Key format
+
+Hacktron API keys look like this:
+
+```
+hacktron_3s9K1sP7m2nXvT8YhLq0ZbW4...
+```
+
+- They always start with the `hacktron_` prefix.
+- The first 12 characters (for example `hacktron_3s9`) are stored as a non‑secret prefix so you can recognise keys in your logs and in the dashboard. The full key is never stored server‑side — only a SHA‑256 hash.
+
+## Making authenticated requests
+
+Send your API key in the `X-Api-Key` header on every request:
+
+```bash
+curl https://api.hacktron.ai/v1/scans \
+ -H "X-Api-Key: hacktron_3s9K1sP7m2nXvT8YhLq0ZbW4..."
+```
+
+You do **not** need to send `X-Organization-Id` or any other header — the organization is resolved from the key.
+
+
+ Never hardcode API keys in source control or client-side code. Load them from environment variables or a secret manager at runtime.
+
+
+## Scopes
+
+Scopes control what an API key is allowed to do. They are declared when you create the key and cannot be changed afterwards — create a new key if you need different scopes.
+
+| Scope | Grants access to |
+| -------- | --------------------------------------------------------------------------------------------------------------------------------- |
+| `read` | All `GET` endpoints: list and read scans, findings, and cost estimations. |
+| `write` | Mutating endpoints: trigger scans, create cost estimations, update findings, add comments. Implies `read` for the same resources. |
+| `delete` | Reserved for future use. |
+
+Each endpoint in this reference states the scope it requires. Calling an endpoint with a key that is missing the required scope returns `403 Forbidden`.
+
+## Revoking a key
+
+You can revoke a key at any time from **Settings → API keys** in the dashboard. Revocation takes effect immediately — the next request with that key will fail with `401 Unauthorized`.
+
+Revoked keys are kept in the dashboard audit trail (with last‑used timestamps) but can never be reactivated.
+
+## Testing your key
+
+To verify a key is working, list scans:
+
+```bash
+curl https://api.hacktron.ai/v1/scans?limit=1 \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+A successful response returns HTTP `200` and a JSON body containing `data`, `total`, `page`, and `limit` fields.
+
+
+ Endpoints can also be exercised interactively in the Swagger UI at [https://api.hacktron.ai/docs](https://api.hacktron.ai/docs). The "Authorize" button accepts an `X-Api-Key` value and persists it for subsequent requests.
+
+
+Common failures:
+
+| Status | Meaning |
+| ------ | -------------------------------------------------------------------------------------------- |
+| `401` | Missing, malformed, revoked, or expired API key. |
+| `403` | Key is valid but is missing the required scope. |
+| `429` | You have hit the rate limit for this key. See [Rate limits](/api-reference/rate-limits). |
diff --git a/api-reference/cost-estimations/create-cost-estimation.mdx b/api-reference/cost-estimations/create-cost-estimation.mdx
new file mode 100644
index 0000000..2842918
--- /dev/null
+++ b/api-reference/cost-estimations/create-cost-estimation.mdx
@@ -0,0 +1,108 @@
+---
+title: "Create cost estimation"
+api: "POST /cost-estimations"
+description: "Estimate the credit cost of scanning one or more repositories before starting a scan."
+---
+
+A cost estimation resolves the repositories you plan to scan, detects applications within them, and returns a predicted credit cost. A completed cost estimation is required before calling [`POST /scans`](/api-reference/scans/create-scan).
+
+Cost estimations run asynchronously. The `POST` returns a record in `pending` or `running` state; poll [`GET /cost-estimations/{id}`](/api-reference/cost-estimations/get-cost-estimation) until `status` reaches `completed`, `partial`, or `failed`.
+
+
+ **Scope required**: `write`
+
+
+## Request
+
+```bash
+curl -X POST https://api.hacktron.ai/v1/cost-estimations \
+ -H "X-Api-Key: $HACKTRON_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "nightly-estimate",
+ "repos": [
+ {
+ "source": "connected",
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main"
+ }
+ ]
+ }'
+```
+
+### Body
+
+| Field | Type | Required | Description |
+| ------- | -------- | -------- | ------------------------------------------------------------------------------------------------ |
+| `name` | string | No | Friendly label for this estimation. Max 255 chars. |
+| `repos` | object[] | Yes | 1–20 repositories to estimate. Each entry is one of the repo shapes below, identified by `source`. |
+
+### Repo shapes
+
+Repositories use a discriminated union on the `source` field.
+
+#### `connected` — a repository already synced to Hacktron via GitHub, GitLab, or Bitbucket
+
+| Field | Type | Required | Description |
+| ------------------------ | ------ | -------- | ---------------------------------------------------- |
+| `source` | `"connected"` | Yes | Discriminator. |
+| `repo_url` | string | Yes | Full HTTPS URL of the repository. Max 500 chars. |
+| `branch` | string | Yes | Branch to estimate. Max 255 chars. |
+| `github_installation_id` | int | No | Specific GitHub App installation ID to use. |
+
+#### `public` — a public git repository Hacktron can clone anonymously
+
+| Field | Type | Required | Description |
+| ---------- | ------ | -------- | ----------------------------------------- |
+| `source` | `"public"` | Yes | Discriminator. |
+| `repo_url` | string | Yes | Full HTTPS URL. Max 500 chars. |
+| `branch` | string | Yes | Branch to estimate. Max 255 chars. |
+
+#### `upload` — a previously uploaded archive
+
+| Field | Type | Required | Description |
+| ------------ | ------ | -------- | ----------------------------------- |
+| `source` | `"upload"` | Yes | Discriminator. |
+| `archive_id` | UUID | Yes | ID of a scan archive you uploaded. |
+
+## Response
+
+`201 Created` — the estimation is queued. Poll `GET /cost-estimations/{id}` until `status` reaches a terminal value.
+
+```json
+{
+ "id": "b4f5c6a1-2d3e-4f56-9a8b-0c1d2e3f4a5b",
+ "organization_id": "f336d0bc-b841-465b-8045-024475c079dd",
+ "user_id": "e5a6d7c8-9b0a-1c2d-3e4f-5a6b7c8d9e0f",
+ "name": "nightly-estimate",
+ "task_id": "cost_est_acme-backend_1712345678",
+ "status": "pending",
+ "repos": [
+ {
+ "source": "connected",
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main"
+ }
+ ],
+ "total_credits": null,
+ "repo_results": null,
+ "error": null,
+ "created_at": "2026-04-13T12:00:00.000Z",
+ "updated_at": "2026-04-13T12:00:00.000Z"
+}
+```
+
+### Terminal statuses
+
+| Status | Meaning |
+| ----------- | ------------------------------------------------------------------------------- |
+| `completed` | All repos were estimated successfully. `total_credits` is populated. |
+| `partial` | Some repos completed, others failed. Check `repo_results[].status` for details. |
+| `failed` | The whole estimation failed. `error` contains a message. |
+
+Once in a terminal state, an estimation is immutable. Pass its `id` as `cost_estimation_id` to [`POST /scans`](/api-reference/scans/create-scan) to start the scan.
+
+## Errors
+
+- `400` — invalid or missing fields (for example, more than 20 repos, invalid `source`, or missing `repo_url`).
+- `401` / `403` — authentication or scope failure.
diff --git a/api-reference/cost-estimations/get-cost-estimation.mdx b/api-reference/cost-estimations/get-cost-estimation.mdx
new file mode 100644
index 0000000..a50b001
--- /dev/null
+++ b/api-reference/cost-estimations/get-cost-estimation.mdx
@@ -0,0 +1,71 @@
+---
+title: "Get cost estimation"
+api: "GET /cost-estimations/{id}"
+description: "Fetch a cost estimation by ID."
+---
+
+Returns a single cost estimation. Poll this endpoint on a pending estimation until `status` reaches a terminal value (`completed`, `partial`, or `failed`).
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/cost-estimations/b4f5c6a1-2d3e-4f56-9a8b-0c1d2e3f4a5b" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | --------------------------------- |
+| `id` | UUID | The cost estimation UUID. |
+
+## Response
+
+`200 OK` — see [Create cost estimation](/api-reference/cost-estimations/create-cost-estimation) for the response schema.
+
+```json
+{
+ "id": "b4f5c6a1-2d3e-4f56-9a8b-0c1d2e3f4a5b",
+ "organization_id": "f336d0bc-b841-465b-8045-024475c079dd",
+ "user_id": "e5a6d7c8-9b0a-1c2d-3e4f-5a6b7c8d9e0f",
+ "name": "nightly-estimate",
+ "task_id": "cost_est_acme-backend_1712345678",
+ "status": "completed",
+ "repos": [
+ {
+ "source": "connected",
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main",
+ "commit_sha": "abc123def456"
+ }
+ ],
+ "total_credits": 4200,
+ "repo_results": [
+ {
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main",
+ "commit_sha": "abc123def456",
+ "status": "completed",
+ "from_cache": false,
+ "credits": 4200,
+ "applications": [
+ {
+ "app_name": "backend-api",
+ "app_root_path": "apps/api",
+ "architecture": "NestJS REST API with PostgreSQL"
+ }
+ ]
+ }
+ ],
+ "created_at": "2026-04-13T12:00:00.000Z",
+ "updated_at": "2026-04-13T12:03:21.000Z"
+}
+```
+
+## Errors
+
+- `404` — estimation not found or not visible to your organization.
diff --git a/api-reference/cost-estimations/list-cost-estimations.mdx b/api-reference/cost-estimations/list-cost-estimations.mdx
new file mode 100644
index 0000000..1d60b4b
--- /dev/null
+++ b/api-reference/cost-estimations/list-cost-estimations.mdx
@@ -0,0 +1,86 @@
+---
+title: "List cost estimations"
+api: "GET /cost-estimations"
+description: "List cost estimations for your organization."
+---
+
+Returns cost estimations for the organization, most recent first. Use offset-based pagination to page through results.
+
+
+ **Scope required**: `read`
+
+
+
+ Unlike most list endpoints, cost estimations use **offset‑based pagination** (`limit` + `offset`) instead of `page` + `limit`. See [Pagination, filtering & sorting](/api-reference/pagination-filtering) for the general conventions.
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/cost-estimations?limit=20&offset=0" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Query parameters
+
+| Parameter | Type | Default | Max | Description |
+| --------- | ------- | ------- | --- | ------------------------------------ |
+| `limit` | integer | `50` | `100` | Number of items to return. |
+| `offset` | integer | `0` | — | Number of items to skip. |
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "data": [
+ {
+ "id": "b4f5c6a1-2d3e-4f56-9a8b-0c1d2e3f4a5b",
+ "organization_id": "f336d0bc-b841-465b-8045-024475c079dd",
+ "user_id": "e5a6d7c8-9b0a-1c2d-3e4f-5a6b7c8d9e0f",
+ "name": "nightly-estimate",
+ "task_id": "cost_est_acme-backend_1712345678",
+ "status": "completed",
+ "repos": [
+ {
+ "source": "connected",
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main",
+ "commit_sha": "abc123def456"
+ }
+ ],
+ "total_credits": 4200,
+ "repo_results": [
+ {
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main",
+ "commit_sha": "abc123def456",
+ "status": "completed",
+ "from_cache": false,
+ "credits": 4200,
+ "applications": [
+ {
+ "app_name": "backend-api",
+ "app_root_path": "apps/api",
+ "architecture": "NestJS REST API with PostgreSQL"
+ }
+ ]
+ }
+ ],
+ "created_at": "2026-04-13T12:00:00.000Z",
+ "updated_at": "2026-04-13T12:03:21.000Z"
+ }
+ ],
+ "total": 42
+}
+```
+
+### Fields
+
+| Field | Type | Description |
+| ------- | -------- | --------------------------------------------------- |
+| `data` | object[] | The estimations on this page. |
+| `total` | integer | Total number of estimations in the organization. |
+
+See [Create cost estimation](/api-reference/cost-estimations/create-cost-estimation) for the full estimation object shape.
diff --git a/api-reference/errors.mdx b/api-reference/errors.mdx
new file mode 100644
index 0000000..c9f7aa0
--- /dev/null
+++ b/api-reference/errors.mdx
@@ -0,0 +1,65 @@
+---
+title: "Errors"
+description: "HTTP status codes and error response shapes."
+---
+
+The Hacktron REST API uses standard HTTP status codes to indicate success and failure.
+
+## Status codes
+
+| Status | Meaning |
+| ------ | ---------------------------------------------------------------------------------------------------------- |
+| `200` | Successful `GET` or `PATCH`. |
+| `201` | Successful `POST` — a new resource was created. |
+| `204` | Successful request with no response body (for example, revoking a key). |
+| `400` | Bad request. Typically a validation failure; `message` identifies the field that failed validation. |
+| `401` | Missing, malformed, revoked, or expired API key. |
+| `402` | Payment required. Returned by `POST /scans` when the organization has insufficient pentest credits. |
+| `403` | Your API key does not have the required scope. |
+| `404` | The resource does not exist, or is not visible to your organization. |
+| `409` | Conflict. For example, exceeding the maximum active API key count when creating a new key. |
+| `429` | Rate limit exceeded. See [Rate limits](/api-reference/rate-limits). |
+| `500` | Unexpected server error. Safe to retry with backoff; contact support if it persists. |
+
+## Error body shape
+
+Errors are returned as JSON with a consistent shape:
+
+```json
+{
+ "statusCode": 400,
+ "message": "At least one repository is required",
+ "error": "Bad Request"
+}
+```
+
+Validation errors may return `message` as an array of field‑level errors:
+
+```json
+{
+ "statusCode": 400,
+ "message": [
+ "repos must contain at least 1 elements",
+ "cost_estimation_id must be a UUID"
+ ],
+ "error": "Bad Request"
+}
+```
+
+## Common failure modes
+
+### "This endpoint requires API key authentication"
+
+A REST endpoint was called with a dashboard session token instead of an API key. REST endpoints under `/v1/*` require the `X-Api-Key` header.
+
+### "API key missing required scope: write"
+
+The key was created with `read` only but the request targets a write endpoint. Create a new key that includes the `write` scope and retry.
+
+### "Insufficient credits to start this scan"
+
+The organization does not have enough pentest credits for the requested scan. Top up from **Billing** in the dashboard, or submit a smaller cost estimation.
+
+### "Finding not found"
+
+Returned when the finding does not exist, has not yet reached the approved verification state, or belongs to a different organization. Hacktron does not distinguish between these cases in the response to avoid leaking cross‑tenant information.
diff --git a/api-reference/findings/add-finding-comment.mdx b/api-reference/findings/add-finding-comment.mdx
new file mode 100644
index 0000000..5df6d39
--- /dev/null
+++ b/api-reference/findings/add-finding-comment.mdx
@@ -0,0 +1,51 @@
+---
+title: "Add finding comment"
+api: "POST /findings/{id}/comments"
+description: "Append a comment to a finding's triage thread."
+---
+
+Appends a comment to a finding's triage thread. Comments appear in the dashboard alongside comments from GitHub, Slack, and other integrations.
+
+
+ **Scope required**: `write`
+
+
+## Request
+
+```bash
+curl -X POST https://api.hacktron.ai/v1/findings/d1e2f3a4-b5c6-7890-1234-567890abcdef/comments \
+ -H "X-Api-Key: $HACKTRON_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "comment": "Confirmed reproducible on staging. Opened JIRA-4821."
+ }'
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------------- |
+| `id` | UUID | Finding UUID. |
+
+### Body
+
+| Field | Type | Required | Description |
+| --------- | ------ | -------- | ------------------------------------------ |
+| `comment` | string | Yes | Comment text. 1–4000 characters. |
+
+## Response
+
+`201 Created`
+
+```json
+{
+ "comment_id": "c0ffee01-feed-4bad-badc-0ffee0000002"
+}
+```
+
+The comment is tagged with `source: "api"` and attributed to the API key's creator. It then appears in the finding's [`triage_thread`](/api-reference/findings/get-finding).
+
+## Errors
+
+- `400` — comment missing or exceeds 4000 characters.
+- `404` — finding not found or not visible to your organization.
diff --git a/api-reference/findings/get-finding.mdx b/api-reference/findings/get-finding.mdx
new file mode 100644
index 0000000..8cf592c
--- /dev/null
+++ b/api-reference/findings/get-finding.mdx
@@ -0,0 +1,90 @@
+---
+title: "Get finding"
+api: "GET /findings/{id}"
+description: "Fetch a single finding with full triage context."
+---
+
+Returns a single finding along with its triage thread, occurrence count, repository URL, scan type, and a Mermaid diagram of the vulnerability trace when one was produced.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/findings/d1e2f3a4-b5c6-7890-1234-567890abcdef" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------------- |
+| `id` | UUID | Finding UUID. |
+
+## Response
+
+`200 OK` — all the fields from [List findings](/api-reference/findings/list-findings#finding-fields), plus:
+
+```json
+{
+ "id": "d1e2f3a4-b5c6-7890-1234-567890abcdef",
+ "title": "SQL injection in /api/v1/checkout",
+ "category": "injection",
+ "severity": "critical",
+ "state": "open",
+ "description": "...",
+ "affected_file": "apps/api/src/checkout/checkout.service.ts",
+ "affected_code": "...",
+ "proof_of_concept": "...",
+ "impact": "...",
+ "root_cause": "...",
+ "remediation": "...",
+ "tags": ["injection", "sql"],
+ "scan_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "found_at": "2026-04-13T12:15:00.000Z",
+ "updated_at": "2026-04-13T12:20:00.000Z",
+ "triage_thread": [
+ {
+ "id": "c0ffee01-feed-4bad-badc-0ffee0000001",
+ "reaction": null,
+ "comment": "Confirmed reproducible on staging — cart_id=1' OR '1'='1 returns 200 with full row dump.",
+ "user_id": "e5a6d7c8-9b0a-1c2d-3e4f-5a6b7c8d9e0f",
+ "username": "alex",
+ "source": "api",
+ "timestamp": "2026-04-13T12:22:00.000Z"
+ }
+ ],
+ "mermaid_trace": "graph TD\n A[Request] --> B[checkout.service.ts]\n B --> C[(Postgres)]",
+ "occurrence_count": 1,
+ "repo_url": "https://github.com/acme/backend",
+ "scan_type": "full"
+}
+```
+
+### Extra fields
+
+| Field | Type | Description |
+| ------------------ | --------------- | --------------------------------------------------------------------------------------------- |
+| `triage_thread` | object[] | Comments and reactions on the finding, aggregated across GitHub, Slack, the web app, and the API. |
+| `mermaid_trace` | string\|null | Mermaid diagram source for the vulnerability trace. `null` when Hacktron did not produce one. |
+| `occurrence_count` | integer | Number of scans in which this finding has appeared. Minimum `1`. |
+| `repo_url` | string\|null | Primary repository URL of the parent scan. |
+| `scan_type` | enum\|null | `pr` or `full`. |
+
+### Triage thread entry
+
+| Field | Type | Description |
+| ----------- | ------------- | ------------------------------------------------------------------------ |
+| `id` | UUID | Entry identifier. |
+| `reaction` | string\|null | Triage reaction. One of the finding states (`open`, `true_positive`, `false_positive`, `accepted_risk`, `resolved`). `null` for comment-only entries. |
+| `comment` | string\|null | Comment text. `null` for reaction-only entries. |
+| `user_id` | UUID | Author user ID. |
+| `username` | string | Author display name. |
+| `source` | enum | Origin of the entry. One of `github`, `slack`, `web`, `api`, `agent`. |
+| `timestamp` | string | ISO 8601 timestamp. |
+
+## Errors
+
+- `404` — finding not found, still in verification, or not visible to your organization.
diff --git a/api-reference/findings/list-findings.mdx b/api-reference/findings/list-findings.mdx
new file mode 100644
index 0000000..5aca470
--- /dev/null
+++ b/api-reference/findings/list-findings.mdx
@@ -0,0 +1,87 @@
+---
+title: "List findings"
+api: "GET /findings"
+description: "List findings across all scans in your organization."
+---
+
+Returns findings across every scan in the organization, with filtering and sorting. Only findings that have passed automated verification (`verification_status = approved`) are returned.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/findings?severity=critical&state=open&sort_by=found_at&sort_order=DESC&page=1&limit=50" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Query parameters
+
+| Parameter | Type | Default | Description |
+| ------------ | ------- | ---------- | ------------------------------------------------------------------------------------------------------- |
+| `page` | integer | `1` | 1‑based page number. |
+| `limit` | integer | `15` | Items per page. Max `100`. |
+| `severity` | enum | — | Filter by severity: `critical`, `high`, `medium`, `low`, `info`. |
+| `state` | enum | — | Filter by state: `open`, `true_positive`, `false_positive`, `accepted_risk`, `resolved`. |
+| `scan_id` | UUID | — | Only return findings produced by this scan. The scan must exist in the organization. |
+| `sort_by` | enum | `found_at` | One of `found_at`, `updated_at`, `severity`. |
+| `sort_order` | enum | `DESC` | `ASC` or `DESC`. |
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "data": [
+ {
+ "id": "d1e2f3a4-b5c6-7890-1234-567890abcdef",
+ "title": "SQL injection in /api/v1/checkout",
+ "category": "injection",
+ "severity": "critical",
+ "state": "open",
+ "description": "User-supplied cart ID is concatenated into a SQL query...",
+ "affected_file": "apps/api/src/checkout/checkout.service.ts",
+ "affected_code": "const rows = await conn.query(`SELECT * FROM carts WHERE id = '${cartId}'`);",
+ "proof_of_concept": "POST /api/v1/checkout with cart_id=1' OR '1'='1",
+ "impact": "Full read access to the carts table, including other customers' items.",
+ "root_cause": "String interpolation instead of parameterised query.",
+ "remediation": "Use parameterised queries via the driver's placeholder API.",
+ "tags": ["injection", "sql"],
+ "scan_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "found_at": "2026-04-13T12:15:00.000Z",
+ "updated_at": "2026-04-13T12:20:00.000Z"
+ }
+ ],
+ "total": 284,
+ "page": 1,
+ "limit": 50
+}
+```
+
+### Finding fields
+
+| Field | Type | Description |
+| ------------------ | ------------- | --------------------------------------------------------------------------- |
+| `id` | UUID | Finding identifier. |
+| `title` | string | Short summary. |
+| `category` | string | Vulnerability category (for example `injection`, `auth`, `xss`). |
+| `severity` | enum | `critical`, `high`, `medium`, `low`, `info`. |
+| `state` | enum | `open`, `true_positive`, `false_positive`, `accepted_risk`, `resolved`. |
+| `description` | string | Long‑form description of the issue. |
+| `affected_file` | string | Path relative to the repository root. |
+| `affected_code` | string | Code snippet of the affected location. |
+| `proof_of_concept` | string\|null | Reproduction steps or payload. `null` if none was captured. |
+| `impact` | string\|null | Impact narrative. |
+| `root_cause` | string\|null | Root-cause narrative. |
+| `remediation` | string\|null | Suggested fix. |
+| `tags` | string[] | Free‑form tags. |
+| `scan_id` | UUID\|null | Scan that produced this finding. |
+| `found_at` | string | ISO 8601 timestamp when the finding was first discovered. |
+| `updated_at` | string | ISO 8601 timestamp of the most recent state or severity change. |
+
+## Errors
+
+- `404` — `scan_id` was provided but the scan does not exist or is not visible.
diff --git a/api-reference/findings/update-finding.mdx b/api-reference/findings/update-finding.mdx
new file mode 100644
index 0000000..c256a23
--- /dev/null
+++ b/api-reference/findings/update-finding.mdx
@@ -0,0 +1,65 @@
+---
+title: "Update finding"
+api: "PATCH /findings/{id}"
+description: "Change a finding's state and/or severity."
+---
+
+Updates the `state`, `severity`, or both on a single finding. Every change is recorded to the finding's audit trail and propagated to connected integrations (GitHub, Slack, Jira, Linear).
+
+
+ **Scope required**: `write`
+
+
+## Request
+
+```bash
+curl -X PATCH https://api.hacktron.ai/v1/findings/d1e2f3a4-b5c6-7890-1234-567890abcdef \
+ -H "X-Api-Key: $HACKTRON_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "state": "true_positive",
+ "severity": "critical",
+ "reason": "Confirmed reproducible against staging. Raising from high to critical."
+ }'
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------------- |
+| `id` | UUID | Finding UUID. |
+
+### Body
+
+At least one of `state` or `severity` must be provided. Providing neither returns `400`.
+
+| Field | Type | Description |
+| ----------------- | ------ | ------------------------------------------------------------------------------------------------------------ |
+| `state` | enum | New state. One of `open`, `true_positive`, `false_positive`, `accepted_risk`, `resolved`. |
+| `severity` | enum | New severity. One of `critical`, `high`, `medium`, `low`, `info`. |
+| `state_reason` | string | Reason for the state change. Max 2000 chars. Falls back to `reason` if omitted. |
+| `severity_reason` | string | Reason for the severity change. Max 2000 chars. Falls back to `reason` if omitted. |
+| `reason` | string | Shorthand applied to both state and severity when their specific reason fields are omitted. Max 2000 chars. |
+
+
+ Pass `reason` alone when the same justification applies to both state and severity. Use `state_reason` and `severity_reason` only when the justifications differ.
+
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "id": "d1e2f3a4-b5c6-7890-1234-567890abcdef",
+ "state": "true_positive",
+ "severity": "critical"
+}
+```
+
+The response contains only the updated state and severity. Fetch the full finding via [`GET /findings/{id}`](/api-reference/findings/get-finding) for the remaining fields.
+
+## Errors
+
+- `400` — neither `state` nor `severity` provided, or field validation failed.
+- `404` — finding not found, still in verification, or not visible to your organization.
diff --git a/api-reference/introduction.mdx b/api-reference/introduction.mdx
new file mode 100644
index 0000000..878abf7
--- /dev/null
+++ b/api-reference/introduction.mdx
@@ -0,0 +1,60 @@
+---
+title: "Introduction"
+description: "Programmatic access to Hacktron scans, findings, and cost estimations."
+---
+
+The Hacktron REST API lets you trigger pentest scans, browse and triage findings, and run cost estimations from your own tooling.
+
+Everything you can do in the Hacktron dashboard as a scan operator or reviewer is available through the API, scoped to a single organization per key.
+
+## Base URL
+
+```
+https://api.hacktron.ai/v1
+```
+
+All endpoints in this reference are relative to this base URL.
+
+## Interactive API reference
+
+A Swagger UI rendered from the live OpenAPI spec is hosted at [https://api.hacktron.ai/docs](https://api.hacktron.ai/docs). It covers the same endpoints documented here with raw request and response schemas and a "Try it out" console, useful for verifying field names or cross-checking schemas against production.
+
+## What you can do
+
+
+
+ Start full pentest scans against one or more repositories and track their status.
+
+
+ Generate a cost estimation for a set of repositories before committing credits.
+
+
+ List, filter, and inspect findings across scans, including their full triage context.
+
+
+ Update finding state, adjust severity, and add comments from your own systems.
+
+
+
+## Requirements
+
+- A Hacktron organization with an **Admin** or **Owner** role (required to create API keys).
+- An API key — see [Authentication](/api-reference/authentication) for how to create one.
+- An HTTP client that can set custom headers.
+
+## Next steps
+
+
+
+ Create an API key and make your first authenticated request.
+
+
+ Understand request quotas and how to handle 429 responses.
+
+
+ Learn the shared query conventions used across list endpoints.
+
+
+ HTTP status codes and error shapes you should handle.
+
+
diff --git a/api-reference/pagination-filtering.mdx b/api-reference/pagination-filtering.mdx
new file mode 100644
index 0000000..7b2bf08
--- /dev/null
+++ b/api-reference/pagination-filtering.mdx
@@ -0,0 +1,104 @@
+---
+title: "Pagination, filtering & sorting"
+description: "Shared query conventions used by list endpoints."
+---
+
+List endpoints in the Hacktron REST API share a consistent set of query parameters for pagination, sorting, and filtering.
+
+## Pagination
+
+Most list endpoints use **page‑based pagination**:
+
+| Parameter | Type | Default | Max | Description |
+| --------- | ------- | ------- | --- | ----------------------------- |
+| `page` | integer | `1` | — | 1‑based page number. |
+| `limit` | integer | `15` | `100` | Number of items per page. |
+
+The response echoes the current `page` and `limit`, and includes `total` for building pagination controls or iterating until exhausted:
+
+```json
+{
+ "data": [/* items for this page */],
+ "total": 284,
+ "page": 2,
+ "limit": 50
+}
+```
+
+To iterate all pages:
+
+```bash
+page=1
+while : ; do
+ curl -s "https://api.hacktron.ai/v1/findings?page=$page&limit=100" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+ # ... extract data, break when (page * limit) >= total
+ page=$((page + 1))
+done
+```
+
+
+ **Cost Estimations** use offset‑based pagination instead, with `limit` (default `50`, max `100`) and `offset` (default `0`). This is called out explicitly on the [List Cost Estimations](/api-reference/cost-estimations/list-cost-estimations) page.
+
+
+## Sorting
+
+Endpoints that support sorting accept two parameters:
+
+| Parameter | Type | Description |
+| ------------ | ------ | ------------------------------------------------- |
+| `sort_by` | enum | Which field to sort by. Valid values vary per endpoint. |
+| `sort_order` | enum | `ASC` or `DESC`. Defaults to `DESC`. |
+
+The allowed `sort_by` values are documented on each list endpoint. Examples:
+
+- **List scans**: `created_at`, `updated_at`, `status`
+- **List findings**: `found_at`, `updated_at`, `severity`
+
+If `sort_by` is omitted, the endpoint-specific default applies (see each list page).
+
+## Filtering
+
+Filters are passed as additional query parameters. Each list endpoint documents the filters it supports. The patterns below are shared across list endpoints.
+
+### Enum filters
+
+Enum filters accept the canonical lower‑snake‑case value and return only records that match:
+
+```bash
+# Only critical findings
+GET /findings?severity=critical
+
+# Only scans that have finished successfully
+GET /scans?status=completed
+```
+
+### Scoped filters
+
+Some filters restrict results to a related resource:
+
+```bash
+# Findings for a specific scan
+GET /findings?scan_id=123e4567-e89b-12d3-a456-426614174000
+```
+
+Scoped filters validate that the target resource belongs to your organization. `404` is returned if the resource does not exist or is not visible to the key.
+
+### Combining filters
+
+Multiple filters combine with AND semantics:
+
+```bash
+GET /findings?severity=high&state=open&scan_id=...
+```
+
+There is no OR operator. Call the endpoint multiple times and merge results client-side if a union is required.
+
+## Example
+
+Fetch the 25 most recently updated high‑severity findings that are still open:
+
+```bash
+curl "https://api.hacktron.ai/v1/findings?severity=high&state=open&sort_by=updated_at&sort_order=DESC&page=1&limit=25" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
diff --git a/api-reference/rate-limits.mdx b/api-reference/rate-limits.mdx
new file mode 100644
index 0000000..1ac37c2
--- /dev/null
+++ b/api-reference/rate-limits.mdx
@@ -0,0 +1,52 @@
+---
+title: "Rate limits"
+description: "Request quotas, 429 behaviour, and how to back off."
+---
+
+The Hacktron REST API rate limits requests per API key.
+
+## Limit
+
+- **100 requests per 60 seconds** per API key, across all REST endpoints.
+
+The window is rolling; there is no fixed start or end. After 100 requests at `t=0`, the next request is accepted at approximately `t=60s`.
+
+Requests authenticated with dashboard sessions (not API keys) do not count against this limit.
+
+## 429 responses
+
+When a key exceeds the limit, the API returns `429 Too Many Requests`:
+
+```http
+HTTP/1.1 429 Too Many Requests
+Content-Type: application/json
+
+{
+ "statusCode": 429,
+ "message": "ThrottlerException: Too Many Requests"
+}
+```
+
+The request body is not consumed; the request is rejected before it reaches the application.
+
+## Backing off
+
+Recommended client behaviour on a `429`:
+
+1. Stop issuing requests with the affected key.
+2. Retry with exponential backoff (for example 1s, 2s, 4s, 8s).
+3. If the limit is hit consistently, spread the workload over a longer window or contact Hacktron support about a higher quota.
+
+For workloads that require higher sustained throughput (for example, backfilling findings into an external system), contact [support](mailto:founders@hacktron.ai) with the use case.
+
+## Concurrency and long‑running scans
+
+`POST /scans` enqueues the scan and returns immediately; no long‑lived connection is required while the scan runs.
+
+The recommended pattern:
+
+1. `POST /scans` to start the scan and capture the returned `id`.
+2. `GET /scans/{id}/status` at a 10–30 second interval to check progress.
+3. Once `status` is `completed`, fetch findings with `GET /scans/{id}/findings` or `GET /scans/{id}/findings/export`.
+
+Polling counts toward the rate limit. Use intervals no shorter than 10 seconds.
diff --git a/api-reference/scans/create-scan.mdx b/api-reference/scans/create-scan.mdx
new file mode 100644
index 0000000..8f89e7c
--- /dev/null
+++ b/api-reference/scans/create-scan.mdx
@@ -0,0 +1,87 @@
+---
+title: "Trigger a pentest scan"
+api: "POST /scans"
+description: "Start a pentest scan against one or more repositories."
+---
+
+Starts a full pentest scan against the repositories attached to a completed cost estimation. Scans run asynchronously; the endpoint returns immediately with a scan `id` for polling.
+
+
+ **Scope required**: `write`
+
+
+## Prerequisites
+
+1. Create a cost estimation with [`POST /cost-estimations`](/api-reference/cost-estimations/create-cost-estimation) and wait for it to reach `completed` status.
+2. Ensure your organization has enough pentest credits (visible in **Billing** in the dashboard). Insufficient credits return `402 Payment Required`.
+
+## Request
+
+```bash
+curl -X POST https://api.hacktron.ai/v1/scans \
+ -H "X-Api-Key: $HACKTRON_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "cost_estimation_id": "b4f5c6a1-2d3e-4f56-9a8b-0c1d2e3f4a5b",
+ "repos": [
+ {
+ "url": "https://github.com/acme/backend",
+ "branch": "main",
+ "source": "connected"
+ }
+ ],
+ "target_urls": ["https://staging.acme.com"],
+ "auth_instructions": "Use test account test@acme.com / hunter2",
+ "custom_context": "Focus on the payment flow under /api/v1/checkout"
+ }'
+```
+
+### Body
+
+| Field | Type | Required | Description |
+| ---------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
+| `cost_estimation_id` | UUID | Yes | ID of a completed cost estimation. The first repo in `repos` must match the estimation. |
+| `repos` | object[] | Yes | At least one repository. The first is the primary target; any additional entries are scanned as related repositories. |
+| `target_urls` | string[] | No | Live URLs to include in the pentest (for example a staging environment). |
+| `auth_instructions` | string | No | Credentials or steps the scanner should follow to authenticate. Max 2000 chars. |
+| `custom_context` | string | No | Additional context: scope, areas to emphasise, exclusions. Max 2000 chars. |
+| `context_document_ids` | UUID[] | No | IDs of context documents to attach to the scan. |
+
+### Repo object
+
+| Field | Type | Required | Description |
+| -------- | ------ | -------- | ----------------------------------------------------------------------- |
+| `url` | string | Yes | HTTPS URL of the repository. |
+| `branch` | string | Yes | Branch to scan. |
+| `source` | enum | No | `connected`, `public`, or `upload`. Defaults to `connected`. |
+
+## Response
+
+`201 Created`
+
+```json
+{
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "task_id": "web_scan_1712345678",
+ "status": "pending",
+ "message": "Scan started"
+}
+```
+
+| Field | Type | Description |
+| --------- | ------ | ------------------------------------------------------------- |
+| `id` | UUID | Scan UUID. Use this to fetch status, details, and findings. |
+| `task_id` | string | Internal task identifier (also visible in the dashboard). |
+| `status` | string | Always `pending` at creation time. |
+| `message` | string | Status message suitable for display. |
+
+## Errors
+
+- `400` — missing `repos`, invalid `cost_estimation_id`, or validation failure.
+- `401` / `403` — authentication or scope failure.
+- `402` — insufficient pentest credits. Top up from the **Billing** page in the dashboard.
+
+## Next steps
+
+- Poll [`GET /scans/{id}/status`](/api-reference/scans/get-scan-status) until status is `completed`.
+- Fetch findings with [`GET /scans/{id}/findings`](/api-reference/scans/list-scan-findings) or export them with [`GET /scans/{id}/findings/export`](/api-reference/scans/export-scan-findings).
diff --git a/api-reference/scans/export-scan-findings.mdx b/api-reference/scans/export-scan-findings.mdx
new file mode 100644
index 0000000..4b9c9e7
--- /dev/null
+++ b/api-reference/scans/export-scan-findings.mdx
@@ -0,0 +1,66 @@
+---
+title: "Export scan findings"
+api: "GET /scans/{id}/findings/export"
+description: "Download all findings for a scan as JSON, CSV, or SARIF."
+---
+
+Returns every approved finding for a scan in one of three formats. Unlike [List scan findings](/api-reference/scans/list-scan-findings), this endpoint is **not paginated**; all findings are returned in a single response.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/scans/a1b2c3d4-e5f6-7890-abcd-ef1234567890/findings/export?format=sarif" \
+ -H "X-Api-Key: $HACKTRON_API_KEY" \
+ -o findings.sarif
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | ------------- |
+| `id` | UUID | Scan UUID. |
+
+### Query parameters
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | --------------------------------------------- |
+| `format` | enum | Yes | One of `json`, `csv`, `sarif`. |
+
+## Response
+
+The `Content-Type` and `Content-Disposition` headers depend on the requested format.
+
+### `format=json`
+
+```http
+Content-Type: application/json; charset=utf-8
+```
+
+Returns a JSON array of finding objects, each matching the schema described in [List findings](/api-reference/findings/list-findings#finding-fields).
+
+### `format=csv`
+
+```http
+Content-Type: text/csv; charset=utf-8
+Content-Disposition: attachment; filename="findings-.csv"
+```
+
+Returns a CSV document with one row per finding and a header row. Intended for spreadsheets, BI tools, and ad‑hoc review.
+
+### `format=sarif`
+
+```http
+Content-Type: application/sarif+json; charset=utf-8
+Content-Disposition: attachment; filename="findings-.sarif"
+```
+
+Returns a [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) document. SARIF is consumed by GitHub code scanning, Azure DevOps, and most IDE security plugins; use this format to integrate Hacktron findings into existing security tooling.
+
+## Errors
+
+- `400` — missing or invalid `format` query parameter.
+- `404` — scan not found or not visible to your organization.
diff --git a/api-reference/scans/get-scan-status.mdx b/api-reference/scans/get-scan-status.mdx
new file mode 100644
index 0000000..acd10ea
--- /dev/null
+++ b/api-reference/scans/get-scan-status.mdx
@@ -0,0 +1,50 @@
+---
+title: "Get scan status"
+api: "GET /scans/{id}/status"
+description: "Lightweight endpoint for polling a scan until it completes."
+---
+
+Returns a minimal payload with the scan's current status. Use this for polling; the response omits the findings summary that `GET /scans/{id}` computes.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/scans/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | ------------- |
+| `id` | UUID | Scan UUID. |
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "task_id": "web_scan_1712345678",
+ "status": "running",
+ "created_at": "2026-04-13T12:00:00.000Z",
+ "updated_at": "2026-04-13T12:18:04.000Z"
+}
+```
+
+See [List scans](/api-reference/scans/list-scans#scan-statuses) for the full list of status values.
+
+## Recommended polling
+
+A poll interval of **10–30 seconds** is sufficient. Pentest scans complete on the order of minutes; tighter polling provides no additional signal and consumes the [rate limit](/api-reference/rate-limits) quota.
+
+Stop polling when `status` reaches a terminal value: `completed`, `failed`, `stopped`, `cancelled`, or `skipped`.
+
+## Errors
+
+- `404` — scan not found or not visible to your organization.
diff --git a/api-reference/scans/get-scan.mdx b/api-reference/scans/get-scan.mdx
new file mode 100644
index 0000000..84d00a0
--- /dev/null
+++ b/api-reference/scans/get-scan.mdx
@@ -0,0 +1,57 @@
+---
+title: "Get scan"
+api: "GET /scans/{id}"
+description: "Fetch a single scan, including its findings summary."
+---
+
+Returns the full record for a single scan, including its findings summary.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/scans/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | --------------- |
+| `id` | UUID | Scan UUID. |
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "task_id": "web_scan_1712345678",
+ "scan_type": "full",
+ "status": "completed",
+ "name": "acme/backend@main",
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main",
+ "pr_number": null,
+ "target_urls": ["https://staging.acme.com"],
+ "findings_summary": {
+ "critical": 1,
+ "high": 3,
+ "medium": 7,
+ "low": 12,
+ "info": 4
+ },
+ "created_at": "2026-04-13T12:00:00.000Z",
+ "updated_at": "2026-04-13T13:42:18.000Z"
+}
+```
+
+See [List scans](/api-reference/scans/list-scans) for a description of each field.
+
+## Errors
+
+- `404` — scan not found or not visible to your organization.
diff --git a/api-reference/scans/list-scan-findings.mdx b/api-reference/scans/list-scan-findings.mdx
new file mode 100644
index 0000000..d5f0f0a
--- /dev/null
+++ b/api-reference/scans/list-scan-findings.mdx
@@ -0,0 +1,75 @@
+---
+title: "List scan findings"
+api: "GET /scans/{id}/findings"
+description: "Paginate through the findings produced by a scan."
+---
+
+Returns findings produced by a specific scan, with the same filtering and sorting controls as [List findings](/api-reference/findings/list-findings).
+
+Only findings that have passed automated verification (`verification_status = approved`) are returned.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/scans/a1b2c3d4-e5f6-7890-abcd-ef1234567890/findings?severity=high&state=open&page=1&limit=50" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Path parameters
+
+| Parameter | Type | Description |
+| --------- | ---- | ------------- |
+| `id` | UUID | Scan UUID. |
+
+### Query parameters
+
+| Parameter | Type | Default | Description |
+| ------------ | ------- | ------- | ---------------------------------------------------------------------------------------- |
+| `page` | integer | `1` | 1‑based page number. |
+| `limit` | integer | `15` | Items per page. Max `100`. |
+| `severity` | enum | — | Filter by severity: `critical`, `high`, `medium`, `low`, `info`. |
+| `state` | enum | — | Filter by state: `open`, `true_positive`, `false_positive`, `accepted_risk`, `resolved`. |
+| `sort_by` | enum | `found_at` | One of `found_at`, `updated_at`, `severity`. |
+| `sort_order` | enum | `DESC` | `ASC` or `DESC`. |
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "data": [
+ {
+ "id": "d1e2f3a4-b5c6-7890-1234-567890abcdef",
+ "title": "SQL injection in /api/v1/checkout",
+ "category": "injection",
+ "severity": "critical",
+ "state": "open",
+ "description": "User-supplied cart ID is concatenated into a SQL query...",
+ "affected_file": "apps/api/src/checkout/checkout.service.ts",
+ "affected_code": "const rows = await conn.query(`SELECT * FROM carts WHERE id = '${cartId}'`);",
+ "proof_of_concept": "POST /api/v1/checkout with cart_id=1' OR '1'='1",
+ "impact": "Full read access to the carts table, including other customers' items.",
+ "root_cause": "String interpolation instead of parameterised query.",
+ "remediation": "Use parameterised queries via the driver's placeholder API.",
+ "tags": ["injection", "sql"],
+ "scan_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "found_at": "2026-04-13T12:15:00.000Z",
+ "updated_at": "2026-04-13T12:20:00.000Z"
+ }
+ ],
+ "total": 11,
+ "page": 1,
+ "limit": 50
+}
+```
+
+See [List findings](/api-reference/findings/list-findings#finding-fields) for the field reference.
+
+## Errors
+
+- `404` — scan not found or not visible to your organization.
diff --git a/api-reference/scans/list-scans.mdx b/api-reference/scans/list-scans.mdx
new file mode 100644
index 0000000..1454bfb
--- /dev/null
+++ b/api-reference/scans/list-scans.mdx
@@ -0,0 +1,94 @@
+---
+title: "List scans"
+api: "GET /scans"
+description: "List scans with pagination, filtering, and sorting."
+---
+
+Returns scans in your organization, with filtering by type and status.
+
+
+ **Scope required**: `read`
+
+
+## Request
+
+```bash
+curl "https://api.hacktron.ai/v1/scans?status=completed&sort_by=created_at&sort_order=DESC&page=1&limit=25" \
+ -H "X-Api-Key: $HACKTRON_API_KEY"
+```
+
+### Query parameters
+
+| Parameter | Type | Default | Description |
+| ------------ | ------- | ------- | ------------------------------------------------------------------- |
+| `page` | integer | `1` | 1‑based page number. |
+| `limit` | integer | `15` | Items per page. Max `100`. |
+| `scan_type` | enum | — | Filter by scan type. Values: `pr`, `full`. |
+| `status` | enum | — | Filter by status. See [Scan statuses](#scan-statuses). |
+| `sort_by` | enum | `created_at` | One of `created_at`, `updated_at`, `status`. |
+| `sort_order` | enum | `DESC` | `ASC` or `DESC`. |
+
+### Scan statuses
+
+| Value | Meaning |
+| ----------------------- | -------------------------------------------------------------- |
+| `pending` | Scan queued, not yet started. |
+| `running` | Scan is in progress. |
+| `pending_verification` | Findings are being verified automatically. |
+| `pending_triage` | Findings are ready for triage. |
+| `completed` | Scan finished successfully. |
+| `failed` | Scan failed. See scan detail for error context. |
+| `stopped` / `cancelled` | Scan was stopped or cancelled before completion. |
+| `skipped` | Scan was skipped (no applicable content). |
+| `draft` | Scan was created but never started. |
+
+## Response
+
+`200 OK`
+
+```json
+{
+ "data": [
+ {
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "task_id": "web_scan_1712345678",
+ "scan_type": "full",
+ "status": "completed",
+ "name": "acme/backend@main",
+ "repo_url": "https://github.com/acme/backend",
+ "branch": "main",
+ "pr_number": null,
+ "target_urls": ["https://staging.acme.com"],
+ "findings_summary": {
+ "critical": 1,
+ "high": 3,
+ "medium": 7,
+ "low": 12,
+ "info": 4
+ },
+ "created_at": "2026-04-13T12:00:00.000Z",
+ "updated_at": "2026-04-13T13:42:18.000Z"
+ }
+ ],
+ "total": 142,
+ "page": 1,
+ "limit": 25
+}
+```
+
+### Scan object fields
+
+| Field | Type | Description |
+| ------------------- | ------------- | ---------------------------------------------------------------- |
+| `id` | UUID | Scan identifier. |
+| `task_id` | string | Internal task ID for correlation with the dashboard. |
+| `scan_type` | enum | `pr` or `full`. |
+| `status` | enum | Current scan status. |
+| `name` | string\|null | Display name. Defaults to `repo@branch`. |
+| `repo_url` | string\|null | Primary repository URL. |
+| `branch` | string\|null | Primary branch scanned. |
+| `pr_number` | integer\|null | GitHub PR number (PR scans only). |
+| `target_urls` | string[]\|null| Live targets (pentest scans). |
+| `findings_summary` | object | Approved finding counts by severity. |
+| `created_at` | string | ISO 8601 timestamp. |
+| `updated_at` | string | ISO 8601 timestamp. |
diff --git a/docs.json b/docs.json
index 2d1c1ae..e915224 100644
--- a/docs.json
+++ b/docs.json
@@ -48,6 +48,49 @@
]
}
]
+ },
+ {
+ "tab": "API Reference",
+ "groups": [
+ {
+ "group": "Getting started",
+ "pages": [
+ "api-reference/introduction",
+ "api-reference/authentication",
+ "api-reference/rate-limits",
+ "api-reference/pagination-filtering",
+ "api-reference/errors"
+ ]
+ },
+ {
+ "group": "Cost Estimations",
+ "pages": [
+ "api-reference/cost-estimations/create-cost-estimation",
+ "api-reference/cost-estimations/list-cost-estimations",
+ "api-reference/cost-estimations/get-cost-estimation"
+ ]
+ },
+ {
+ "group": "Scans",
+ "pages": [
+ "api-reference/scans/create-scan",
+ "api-reference/scans/list-scans",
+ "api-reference/scans/get-scan",
+ "api-reference/scans/get-scan-status",
+ "api-reference/scans/list-scan-findings",
+ "api-reference/scans/export-scan-findings"
+ ]
+ },
+ {
+ "group": "Findings",
+ "pages": [
+ "api-reference/findings/list-findings",
+ "api-reference/findings/get-finding",
+ "api-reference/findings/update-finding",
+ "api-reference/findings/add-finding-comment"
+ ]
+ }
+ ]
}
],
"global": {
diff --git a/index.mdx b/index.mdx
index 2b2e447..193c01c 100644
--- a/index.mdx
+++ b/index.mdx
@@ -45,6 +45,45 @@ Meet the next generation of AI-native tools built for developers, security engin
+## REST API
+
+Programmatic access to scans, findings, and cost estimations.
+
+
+
+ Base URL, conventions, and what you can do with the public REST API.
+
+
+ Create an API key, set the `X-Api-Key` header, and understand scopes.
+
+
+ Trigger pentest scans, poll status, and list results.
+
+
+ Browse, filter, triage, and comment on findings.
+
+
+
## Hacktron Workbench (Deprecated)