# Getting started (/docs/getting-started) Integrate Oversecured into your pipeline through the API and the CLI. ## Start here ## Guides Task-oriented — the path a real integration walks. ## References Exact API endpoints, CLI commands, and integration contracts. --- # Quickstart — your first scan (/docs/quickstart) Run your first scan end to end with the CLI. Run one scan end to end with the CLI. The `oversecured scan` command does the whole upload → version → scan dance, so the path is short: install, authenticate, scan, read. Prefer raw REST? Every step below maps to an endpoint — see the [API Overview](/api-overview). ## Before you start You need two things: - A **workspace API token** — starts with `osp_`. Create it in **Workspace settings → API keys** (see [API Overview](/api-overview#authentication)). The token is already scoped to one workspace, so you never pass a `workspaceId`. - An **`appId`** — copy it from the app's dashboard URL, or list apps with `oversecured apps` (see [Apps](/api-apps)). ## 1. Install ```bash npm i -g @oversecured/cli # global — provides the `oversecured` command # or run without installing: npx @oversecured/cli ``` Requires **Node.js ≥ 20**. See [CLI Overview](/cli-overview) for version pinning in CI. ## 2. Authenticate Provide the token. For a local machine, persist it once: ```bash oversecured config set token osp_xxxxxxxx # → ~/.oversecured/config.json ``` In CI, pass it as an env var instead (store it as a secret): ```bash export OVERSECURED_API_TOKEN=osp_xxxxxxxx ``` Confirm it works and find your app id: ```bash oversecured apps ``` ## 3. Scan and wait One command uploads the binary, creates the version (parsed from the file), starts the scan, and blocks until it finishes: ```bash oversecured scan app-release.apk --wait ``` A scan runs for minutes to hours. `--wait` polls until the scan reaches a terminal state (`completed`, `failed`, or `canceled`); for long scans in CI, a webhook is better than blocking — see [Waiting for results](/guide-waiting-for-results). Without `--wait`, the command prints the `scanId` and exits immediately. ## 4. Read the findings List the high-severity findings for the scan: ```bash oversecured findings --app --severity high ``` Add `--json` for the machine-readable envelope, or download a full report: ```bash oversecured report --app --format pdf --output report.pdf ``` See [Commands](/cli-commands) for every flag and [Results](/api-results) for the full finding shape. ## Next - [CI/CD integration](/guide-ci-cd) — fail the build on HIGH with `oversecured scan … --fail-on high`. - [Triaging findings](/guide-triaging-findings) — folders and false positives. - [Exporting reports](/guide-exporting-reports) — PDF / CSV / JSON. --- # Concepts (/docs/concepts) The entities every API/CLI integration touches. The minimum set of entities an API/CLI integration works with. Kept short on purpose — full product concepts live elsewhere. ## Workspace The top-level container — your account's apps, members, tokens, and webhooks live inside it. Every API path is workspace-scoped (`/workspaces/{workspaceId}/…`) and a token is bound to one workspace. The **`workspaceId`** is in the dashboard URL: open any workspace and it's the segment after `/workspaces/` — `…/workspaces/{workspaceId}/…`. ## App One mobile product on one platform — `android` or `ios`. The same product on two platforms is two apps. Every scan belongs to an app. The **`appId`** is the value almost every call needs. The quickest source is the UI: open the app in the dashboard and copy the `appId` segment from the URL. You can also get it via `GET /workspaces/{workspaceId}/apps` or `oversecured apps`. An app also carries a `packageName` (bundle ID, e.g. `com.acme.banking`) and a `platform`. ## Version An uploaded build — Android `.apk`/`.aab` (also `.apks`/`.apkm`/`.xapk`) or iOS `.zip`. You don't create versions by hand: uploading a binary and committing a scan creates the version, and the version metadata (version name, etc.) is parsed from the binary. The CLI infers the platform from the file extension; for anything else, pass `--platform android|ios`. Webhook events are phrased in terms of versions (`version_added`, `version_status_finish`). ## Scan A single run of the scanners over one version. A scan moves through a lifecycle: ``` queued → running → completed ↘ failed ↘ canceled ``` The **`scanId`** is returned by `commit` and identifies the run for status, results, triage, and export. The scanners combine **SAST** (static analysis of the binary) and **DAST** (dynamic analysis of the running app); a single scan can produce both. ## Finding (vulnerability) One result inside a scan. A finding belongs to a **category** (the vulnerability class, with its own severity, title, description, and remediation), carries evidence (code snippets, stack traces, proofs of concept), an optional AI-written explanation, a SAST/DAST taint flow (`sources` → `sink`), and lives in a folder. A finding's `id` is scoped to its scan — it is *not* stable across rescans, and the API exposes no per-finding hash. The platform still tracks whether the same issue recurs internally: that's what powers the new-vs-fixed deltas in a webhook's `version_compare` and the automatic carry-over of folder triage (false-positive marks and custom folders) to later scans. You just can't key on it from the response. ## Severity Three levels: **`high`**, **`medium`**, **`low`**. Severity comes from the finding's category, not from the individual instance. It's the usual lever for a CI gate — most pipelines fail the build only on `high`. Filter findings by severity with `severityFilter=high,medium` (CSV). ## Confirmation (SAST / DAST) How a finding was reached. Each finding exposes `hasSast` and `hasDast`: - **SAST only** — found by static analysis. A real code path, not exercised at runtime. - **DAST** — reproduced on the running app. The strongest signal: there is a working trigger. - **SAST + DAST** — both. Filter by this with `analysisType=sast` or `analysisType=dast`. Failing the build only on DAST-confirmed findings means less noise but narrower coverage — see [CI/CD integration](/guide-ci-cd). ## Folder A triage bucket inside a scan. Every scan starts with **False positives folder** and you can add custom ones. Move findings between folders — or mark them as false positives — with one `move-vulnerabilities` call, where `destination` is a folder UUID, the literal `false_positives`, or `unassigned`. Marking a false positive *is* a move into the system **False positives** folder. See [Triaging findings](/guide-triaging-findings). --- # Waiting for results (/docs/guide-waiting-for-results) Learn a scan finished: scan --wait vs webhooks. A scan runs for minutes to hours. Two ways to learn it finished — pick one. ## `scan --wait` (the simple path) For a one-shot run — a CI job, a local check — let the CLI block until the scan reaches a terminal state and set the exit code from the outcome: ```bash oversecured scan app-release.apk "$APP_ID" --wait ``` `--wait` polls for you and exits when the scan is `completed`, `failed`, or `canceled`. A non-`completed` outcome exits non-zero, so the build won't read partial results. | Flag | Default | Meaning | |---|---|---| | `--timeout ` | `1800` (30 min) | How long to block before giving up. | | `--poll-interval ` | `5` | Status poll cadence. | | `--fail-on ` | — | Also exit non-zero when a finding at/above this severity is reported (implies `--wait`) — see [CI/CD integration](/guide-ci-cd). | ### Mind the timeout The default `--timeout` is **30 minutes** — shorter than a lot of real scans. Android scans in particular routinely run for hours, so the default will time out and fail the job before the scan even finishes. Two ways to handle a long scan: - **Raise the timeout.** We recommend **4 hours** for Android: ```bash oversecured scan app-release.apk "$APP_ID" --wait --timeout 14400 # 4h ``` - **Don't block at all — use a webhook.** Start the scan without `--wait` (the CLI prints the `scanId` and exits immediately) and let Oversecured notify you when the report is ready. This is the better fit when scans are long or you run many of them. ```bash oversecured scan app-release.apk "$APP_ID" # returns scanId, doesn't wait ``` ## Webhooks (for long-lived systems) A server, bot, or SIEM ingestor that stays up should subscribe once instead of holding a process open. Oversecured `POST`s an event when a scan finishes (or errors) — no open loop, no wasted requests, no timeout to tune, and it scales to many apps. > The CLI has no webhook command — register and verify webhooks over the API or in the dashboard. See [API → Webhooks](/api-webhooks). **Setup:** 1. Register an `https://` endpoint — `POST …/applications/{appId}/webhooks`. Store the returned signing `secret`. 2. On delivery, verify the `x-webhook-signature` header — HMAC-SHA256, keyed with the secret, over `{x-webhook-timestamp}.{raw body}` — then act on `action`. 3. Return any `2xx` to acknowledge. Non-2xx and timeouts are retried with exponential backoff. ### The messages you'll receive Every delivery carries an `action` field naming the event: | `action` | Meaning | |---|---| | `version_added` | A new build was uploaded. | | `version_status_scanning` | The scan started running. | | `version_status_finish` | **Scan completed — the report is ready.** | | `version_status_error` | The scan failed. | | `version_deleted` | A version was removed. | | `test` | Synthetic event from the webhook test endpoint — use it to verify connectivity. | The one you gate on is `version_status_finish`. A completed delivery looks like: ```json { "action": "version_status_finish", "integration": { "id": "a1b2c3d4-…", "name": "Acme Banking", "platform": "android", "app_id": "com.acme.banking", "smart_filter_enabled": true, "notify_when_done": true }, "branch_name": "release/4.2.0", "version": { "id": "8a1f0c2e-…", "file_name": "app-release.apk", "platform": "android", "version_id": "4.2.0", "status": "FINISH", "version_compare": { "high": { "fixed": 1, "created": 0 }, "medium": { "fixed": 2, "created": 3 }, "low": { "fixed": 0, "created": 5 } }, "completed_at": "2026-06-03T10:22:31.000Z" } } ``` - `version.id` is the scan/version id — use it to pull the report (`oversecured report --app "$APP"`) or the findings. - `version_compare` is the per-severity delta vs the previous version (`created` = new findings, `fixed` = gone) — enough to decide pass/fail without fetching anything. A `version_status_error` delivery carries the same shape with `status` reflecting the failure; treat it as a failed build. ## Choosing between them | Situation | Use | |---|---| | Short scans, ephemeral CI runner | `scan --wait` (raise `--timeout` if needed). | | Long scans (Android, hours) | Webhook, or `scan --wait --timeout 14400`. | | Long-lived server / bot / SIEM | Webhook. | | Many apps, central ingestion | Webhook (workspace-wide list at `GET /workspaces/{workspaceId}/webhooks`). | --- # CI/CD integration (/docs/guide-ci-cd) Run a scan in CI and fail the build on findings. Run an Oversecured scan inside your pipeline and stop the build automatically when the scan finds serious issues. One command does it. ## The command ```bash oversecured scan app-release.apk "$APP_ID" --wait --fail-on high ``` - `--wait` blocks until the scan finishes. - `--fail-on high` makes the command exit non-zero if the scan reports any **HIGH** finding. A non-zero exit is what fails the CI job and blocks the release. Use `medium` or `low` for a stricter rule. Long scans: the default `--timeout` is 30 minutes, which Android scans often exceed. Raise it (`--timeout 14400` for 4 hours) or skip `--wait` and react to a webhook instead — see [Waiting for results](/guide-waiting-for-results). ## What `--fail-on` counts It fails on **any finding at or above the chosen severity**, including ones you've already marked as false positives. It's severity-only — there's no flag to count just DAST-confirmed findings or to skip triaged ones. If you need that, build your own check (see below). ## Exit codes The job passes on `0` and fails on anything else. The one to know: **`5`** means the scan ended in a non-`completed` state *or* the `--fail-on` threshold was met. Full table in [CLI → CI/CD reference](/cli-ci-cd#exit-codes). ## Recipes Store the token as a CI secret, never inline it. ### GitHub Actions ```yaml - name: Oversecured scan env: OVERSECURED_API_TOKEN: ${{ secrets.OVERSECURED_API_TOKEN }} run: | npx @oversecured/cli@latest scan app-release.apk "$APP_ID" \ --wait --fail-on high --json > scan.json npx @oversecured/cli@latest report "$(jq -r .data.id scan.json)" \ --app "$APP_ID" --format pdf --output report.pdf ``` The job fails (exit `5`) when a HIGH finding appears; `report.pdf` is left to upload as an artefact. ### GitLab CI ```yaml oversecured_scan: image: node:24-alpine script: - npx @oversecured/cli@latest scan app-release.apk "$APP_ID" --wait --fail-on high # OVERSECURED_API_TOKEN set as a masked, protected CI/CD variable ``` ### Any other runner Any runner with Node ≥ 20 works — token in the environment, one command: ```bash export OVERSECURED_API_TOKEN=osp_xxxx npx @oversecured/cli@latest scan "$FILE" "$APP_ID" --wait --fail-on high ``` Pin the version (`@0.1.0-beta.3`) for reproducible builds while the CLI is in beta. ## Custom conditions (optional) `--fail-on` is severity-only. When you need to **ignore false positives** or **fail only on runtime-confirmed (DAST) findings**, don't use `--fail-on` — run the scan, then count findings yourself and fail on your own condition: ```bash oversecured scan app-release.apk "$APP_ID" --wait # scan, don't fail yet high=$(oversecured findings "$SCAN" --app "$APP_ID" --severity high --json | jq '.data.total') [ "$high" -eq 0 ] # non-zero exit fails the job ``` Filtering by DAST or excluding the False positives folder is done via the API — see [Triaging findings](/guide-triaging-findings#how-triage-interacts-with-the-ci-gate). --- # Triaging findings (/docs/guide-triaging-findings) Folders, move, and mark false positives. Organize a scan's findings and suppress noise. The CLI reads and filters findings; **moving** findings between folders and **marking false positives** are mutations the CLI doesn't expose yet — do those over the API or in the dashboard. Full endpoint reference: [API → Triage](/api-triage). ## Reading findings by folder and severity List a scan's findings, narrowed to what you care about: ```bash oversecured findings "$SCAN" --app "$APP" --severity high oversecured findings "$SCAN" --app "$APP" --folder "$FOLDER_ID" # one folder oversecured findings "$SCAN" --app "$APP" --json # machine envelope ``` `--severity` takes a CSV (`high,medium`); `--folder` takes a folder id. To get the folder ids in the first place, list them over the API (no CLI command): ```bash curl -fsS "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/folders" -H "x-api-token: $OS_TOKEN" ``` ## Moving findings into a folder (API) Create a folder, then move a batch of findings with `move-vulnerabilities`, where `destination` is the folder UUID: ```bash # create a folder curl -fsS -X POST "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/folders" \ -H "x-api-token: $OS_TOKEN" -H 'Content-Type: application/json' \ --data '{"name":"Accepted risk"}' # move findings into it curl -fsS -X POST "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/move-vulnerabilities" \ -H "x-api-token: $OS_TOKEN" -H 'Content-Type: application/json' \ --data '{"destination":"","vulnIds":["",""]}' ``` Then read that folder back with `oversecured findings "$SCAN" --app "$APP" --folder `. ## Marking false positives (API) There's no separate endpoint — a false positive *is* a move into the system **False positives** folder. Set `destination` to the literal `false_positives`: ```bash curl -fsS -X POST "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/move-vulnerabilities" \ -H "x-api-token: $OS_TOKEN" -H 'Content-Type: application/json' \ --data '{"destination":"false_positives","vulnIds":[""]}' ``` **Non-obfuscated apps produce more false positives.** Static analysis over an un-obfuscated build sees every code path, including dead or test-only ones, so SAST-only findings (`hasDast:false`) are the usual candidates for review. Confirm against the evidence on the finding detail before suppressing — a DAST-confirmed finding has a working trigger and is rarely a false positive. Inspect one finding with `oversecured finding --scan "$SCAN" --app "$APP"`. ## How triage interacts with the CI gate The findings list has no single "exclude false positives" flag — `--severity high` counts every HIGH, including ones you've suppressed. Two ways to keep a gate honest: - **Subtract the False positives folder.** Count HIGH overall, then count HIGH with `--folder `, and gate on the difference. - **Gate per folder.** Move accepted findings into a custom folder and count only the folders you treat as blocking. Note that compliance summaries already exclude the False positives folder, so a compliance-driven gate ignores suppressed findings automatically. You don't have to re-triage the same issue on every scan either: Oversecured carries every folder placement — false-positive marks and custom folders alike — forward to later scans of the same app automatically (server-side, by an internal hash you don't see in the API). Each scan still owns its own folder rows, but your structure follows: every custom folder you've created is recreated by name in each new scan, even when none of its findings reappear there. Renaming a folder renames it for future scans too; deleting one removes it from future scans. --- # Exporting reports (/docs/guide-exporting-reports) Export reports and push findings to a tracker. Pull a scan's results out of Oversecured for archival, tickets, or a SIEM. The CLI covers the whole-scan report; per-finding export and attachment download are API-only. Endpoint reference: [API → Results](/api-results). ## Export the report ```bash oversecured report "$SCAN" --app "$APP" --format pdf --output report.pdf ``` `--format` is one of `pdf`, `json`, `csv`, `md`. `pdf` and `csv` are binary — always give an `--output` path; text formats (`json`, `md`) print to stdout if you omit it. ```bash oversecured report "$SCAN" --app "$APP" --format json > report.json ``` Use `pdf` for a human-readable artefact, `json`/`csv` to feed a system. Exports include every finding regardless of triage — suppressed findings are labeled, not filtered. Each finding carries its triage marks: `folder` (`name` + `kind`) and `falsePositive` in JSON, `folder`/`falsePositive` columns in CSV, and a folder / false-positive label next to the severity in PDF and Markdown. ## Exporting a single finding The `finding` command exports one finding — pass `--format` (same `json`/`pdf`/`csv`/`md` set) to get a formatted document instead of raw JSON. Handy for pasting into a ticket: ```bash oversecured finding "$VULN" --scan "$SCAN" --app "$APP" --format md --output finding.md ``` `--output -` writes to stdout. The equivalent API endpoint is `GET …/scans/{scanId}/vulnerabilities/{vulnId}/export?format=md`. ## Attachments (API) Findings can carry files (screenshots, captured traffic). There's no CLI command — get a presigned URL over the API, then `GET` it directly: ```bash dl=$(curl -fsS "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/attachments/$ATT/download-url" \ -H "x-api-token: $OS_TOKEN" | jq -r .downloadUrl) curl -fsS "$dl" -o attachment.bin ``` ## Pushing findings to a tracker To sync into Jira / Linear / a SIEM, list the findings as JSON and map each item to an issue. The CLI gives you the machine envelope directly: ```bash oversecured findings "$SCAN" --app "$APP" --json \ | jq -c '.data.items[] | { title: .category.descriptionTitle, severity: .category.severity, sast: .hasSast, dast: .hasDast, id }' ``` The API exposes **no stable cross-scan finding id** — a finding's `id` is scoped to its scan. To avoid duplicate tickets on re-runs, derive your own key from stable fields, e.g. `category.id` plus the primary `sources`/`sink` location. (Oversecured separately carries your folder triage — false-positive marks and custom folders alike — forward across scans on its own, so suppressed findings won't resurface as new tickets.) Fetch a single finding's full evidence and remediation text to fill the ticket body: ```bash oversecured finding "$VULN" --scan "$SCAN" --app "$APP" --json ``` …or attach `oversecured finding "$VULN" --scan "$SCAN" --app "$APP" --format md --output -` as the issue description. --- # References (/docs/references) API endpoints, CLI commands, and integration contracts. Use these pages when you need exact endpoints, request shapes, CLI flags, and automation contracts. --- # API — Overview (/docs/api-overview) Base URL, authentication, IDs, pagination, errors. The Oversecured Public API automates mobile app security from your own systems: upload a build, run a scan, read the findings, triage them, and push results into your tracker. ## Base URL ``` https://api.platform.oversecured.com ``` For an on-prem deployment, point at your own host (the CLI's `OVERSECURED_API_URL`, e.g. `http://localhost:3000`). The API is unversioned — paths are stable and changes are additive. ## Authentication The API authenticates with a **workspace API token**. Create one in the dashboard under **Workspace settings → API keys → Create token**, restrict it to the apps and permissions it needs, and copy it — it's shown **once** and starts with `osp_`. Oversecured stores only a SHA-256 hash and cannot show it again. Send it in the `x-api-token` header on every request: ``` x-api-token: osp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` A token is bound to one workspace and specific apps. Calling an app it isn't bound to returns `403 WORKSPACE_FORBIDDEN`. Rotate by creating a new token and revoking the old one; revoked and expired tokens return `401`. Creating, listing, and revoking tokens is a dashboard operation — intentionally not part of this token-driven API. ## Identifiers you'll need | Value | Where to find it | |---|---| | `workspaceId` | Workspace URL or **Workspace settings**; or resolve it from the token — see below. | | `appId` | App page URL, or `GET /workspaces/{workspaceId}/apps`. | | `scanId` | Returned by `commit`; or `GET …/scans`. | A token already knows its workspace. Resolve the workspace and the apps the token is bound to: ``` GET /me/workspace x-api-token: osp_… ``` ## Pagination List endpoints that paginate (notably the findings list) take `page` and `pageSize` query params and return an envelope: ```json { "items": [ … ], "total": 128, "page": 1, "pageSize": 50 } ``` Read `total` to size your loop. Other list endpoints (apps, scans, folders) return `{ "items": [ … ], "total": N }`. ## Filtering The findings list accepts these query params (combine as needed; CSV filters take comma-separated values): | Param | Example | Meaning | |---|---|---| | `severityFilter` | `high,medium` | CSV of severities. | | `categoryFilter` | `,` | CSV of category IDs. | | `folderId` | `` / `unassigned` / `all` | One folder, unassigned, or everything. | | `analysisType` | `sast` / `dast` | How the finding was reached. | ## Errors Errors use standard HTTP status codes and a JSON body with a stable, machine-readable `code`: ```json { "code": "WORKSPACE_FORBIDDEN", "message": "API token is not bound to this app." } ``` | HTTP | When | |---|---| | 400 | Malformed request. | | 401 | Missing / invalid / expired / revoked token. | | 403 | Token not authorized for this workspace or app. | | 404 | Resource not found. | | 422 | Body failed schema validation. | | 429 | Rate limit — back off and retry. | Branch on `code`, not on `message` — messages may change. ## Rate limits A `429` means you're over the limit. Back off and retry with exponential delay (the binary upload itself goes straight to S3 and isn't rate-limited). For long scans, prefer a [webhook](/api-webhooks) over a tight polling loop. --- # API — Apps (/docs/api-apps) List, create, get, update, delete apps and settings. Manage apps and their settings. This is where the `appId` every other call needs comes from. All paths are workspace-scoped; the token must be bound to the workspace (and, for app-level calls, to the app). ## List apps ``` GET /workspaces/{workspaceId}/apps ``` Returns every app the token can see: `{ "items": [App], "total": N }`. Each `App` carries `id`, `name`, `packageName`, `platform`, plus a snapshot of its latest scan (`latestScanId`, `latestScanStatus`, `latestScanCompletedAt`, `latestScanSeverityCounts`). Match on `packageName` to find an app by bundle ID. ```bash curl -fsS "$BASE/workspaces/$WS/apps" -H "x-api-token: $OS_TOKEN" ``` ## Create an app ``` POST /workspaces/{workspaceId}/apps ``` ```json { "name": "Acme Banking", "platform": "android", "packageName": "com.acme.banking" } ``` `name` and `platform` (`android` | `ios`) are required; `packageName` and `description` are optional. Returns `201` with the created `App`. The token needs the **Applications → Create** permission. ## Get an app ``` GET /workspaces/{workspaceId}/apps/{appId} ``` Returns the single `App`, or `404` if it doesn't exist. ## Update an app ``` PATCH /workspaces/{workspaceId}/apps/{appId} ``` Partial update — send only the fields you change (`name`, `platform`, `packageName`, `description`): ```json { "name": "Acme Banking (Prod)" } ``` ## Delete an app ``` DELETE /workspaces/{workspaceId}/apps/{appId} ``` Returns `204`. **Cascades** to the app's scans and folders — this is irreversible. ## App settings Per-app scan configuration: stored credentials for authenticated DAST, DAST/IAST toggles, smart-filter, and AI enrichment. ``` GET /workspaces/{workspaceId}/apps/{appId}/settings PATCH /workspaces/{workspaceId}/apps/{appId}/settings ``` `PATCH` is a partial update — send only what changes: ```json { "smartFilterEnabled": true, "dastEnabled": false } ``` | Field | Type | Effect | |---|---|---| | `credentials` | `{name,value}[]` | Injected during authenticated DAST. | | `dastEnabled` | boolean | Run dynamic analysis. | | `iastEnabled` | boolean | Run interactive analysis. | | `smartFilterEnabled` | boolean | Drop low-signal findings. | | `aiEnrichmentEnabled` | boolean | AI-written explanations and remediation. | These apply to the app's next scan. --- # API — Scans (/docs/api-scans) Upload, commit, track, rescan, cancel, delete scans. The scan lifecycle: upload a build, commit it, then track the run. Reading the results lives in [Results](/api-results). All paths are under `…/workspaces/{workspaceId}/apps/{appId}`. ## Start a scan (upload → commit) Three steps: get a presigned URL, `PUT` the binary to S3, then commit. **1. Issue an upload URL** ``` POST …/scans/upload-url ``` ```json { "fileName": "app-release.apk" } ``` Returns a `scanId`, a presigned `uploadUrl`, and `expiresInSeconds`: ```json { "scanId": "8a1f0c2e-1b3d-4f5a-9c6e-2d4b6a8c0e1f", "fileName": "app-release.apk", "uploadUrl": "https://s3…", "expiresInSeconds": 900 } ``` **2. Upload the binary straight to S3** — no Oversecured headers: ```bash curl -fsS -X PUT "$uploadUrl" --data-binary "@app-release.apk" ``` **3. Commit the scan** ``` POST …/scans/commit ``` ```json { "scanId": "8a1f0c2e-…", "fileName": "app-release.apk" } ``` Returns `201` with the `Scan` (status `queued`). **The `scanId` is what every later call uses** — status, results, triage, export. > No build to hand? `POST …/scans/mock` queues a synthetic scan with sample findings — handy for wiring up an integration end to end. ## Get scan status ``` GET …/scans/{scanId} ``` Returns the `Scan`. Poll `status` until it's terminal: | Status | Meaning | |---|---| | `queued` | Accepted, not started. | | `running` | Scanners working. | | `completed` | Done — results are ready. | | `failed` | Errored (see `failureReason`). | | `canceled` | Canceled by you or the system. | The `Scan` also carries `vulnerabilityCount`, `severityCounts` (`{high,medium,low}`), `startedAt`, `completedAt`, and `version`. Don't tight-loop — see [Waiting for results](/guide-waiting-for-results). ## List scans ``` GET …/scans ``` Returns `{ "items": [Scan], "total": N }`, newest first. The first item is the latest scan for the app. To fetch one page instead of the whole list, pass `page` (1-based) and/or `pageSize` (1–100, default 20). The response then also carries `page` and `pageSize`; `total` still counts every matching scan. A `page` past the end returns the last page. ## Rescan ``` POST …/scans/{scanId}/rescan ``` Re-runs the scanners against the **same uploaded artefact** — no re-upload. Body is optional (`{ "mock": false }`). Returns `201` with a new `Scan`. Use this after changing app settings; upload a fresh build instead when the binary changed. ## Cancel a scan ``` POST …/scans/{scanId}/cancel ``` Cancels a running or queued scan. Returns `204`. ## Delete a scan ``` DELETE …/scans/{scanId} ``` Cancels (if running) and deletes the scan. Returns `204`. Irreversible. For the recommended way to wait for completion, see [Waiting for results](/guide-waiting-for-results). --- # API — Results (/docs/api-results) Report, vulnerabilities, exports, attachments. Read a finished scan: the report summary, the findings list, a single finding, exports, and attachments. All paths are under `…/workspaces/{workspaceId}/apps/{appId}/scans/{scanId}`. ## Get the scan report ``` GET …/report ``` The AI-generated summaries of the scan: ```json { "llmSummaryTechnical": "…", "llmSummaryNonTechnical": "…" } ``` Per-severity counts live on the `Scan` object (`severityCounts`), not here. ## Report tags Tags can be assigned when a scan is committed or replaced later. The platform trims surrounding whitespace and collapses repeated spaces. Letter case, spaces, and hyphens are otherwise preserved. A report can have up to 20 tags, with up to 100 characters per tag. ```http GET …/tags PUT …/tags ``` The `GET` response and `PUT` request/response use the same shape: ```json { "tags": ["Release", "Mobile Team"] } ``` `PUT` replaces the complete tag list. Send an empty array to remove every tag from the report. List an app's scans with `GET …/apps/{appId}/scans?tags=Release,Mobile%20Team`. When multiple tags are supplied, reports containing any of them are returned. The workspace tag vocabulary and its colors are available from `GET …/workspaces/{workspaceId}/scan-tags`: ```json { "tags": ["Release", "Mobile Team"], "tagColors": { "Release": "blue", "Mobile Team": "green" } } ``` Rename a tag, optionally changing its color, throughout the workspace: ```http PATCH …/workspaces/{workspaceId}/scan-tags ``` ```json { "tag": "Release", "newTag": "Production release", "color": "purple" } ``` Delete a tag from the workspace vocabulary and every report using it: ```http DELETE …/workspaces/{workspaceId}/scan-tags ``` ```json { "tag": "Production release" } ``` Supported colors are `blue`, `green`, `yellow`, `purple`, `red`, and `cyan`. ## List findings ``` GET …/vulnerabilities ``` Paginated findings. Combine the filters from [Overview → Filtering](/api-overview#filtering): `page`, `pageSize`, `severityFilter`, `categoryFilter`, `folderId`, `analysisType`. ```bash curl -fsS "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/vulnerabilities?severityFilter=high&pageSize=50" \ -H "x-api-token: $OS_TOKEN" ``` Response is a `VulnerabilityListPage`: ```json { "items": [ { "id": "b1c2d3e4-…", "folderId": "…", "category": { "id": "…", "severity": "high", "descriptionTitle": "Exported activity…" }, "chainType": null, "codeOrigin": "custom", "thirdPartyLabel": null, "hasSast": true, "hasDast": false, "hasLlmDescription": true, "sources": [{ "code": "android.app.Activity.__getIntentHooked()", "line": 146 }], "sink": { "code": "java.lang.String.endsWith(...)", "file": "…/DeeplinkActivity.java", "line": 45 }, "isLocked": false } ], "total": 128, "page": 1, "pageSize": 50 } ``` Each row is compact: severity and title live under `category`; `hasSast`/`hasDast` give the confirmation; `sources`/`sink` carry the taint flow (empty/null for config-only findings); `isLocked` is `true` when a trial app redacts technical detail for high/medium findings. Fetch the detail endpoint for full evidence (code snippets, stack traces, PoCs). ## Get one finding ``` GET …/vulnerabilities/{vulnId} ``` Full detail for a single finding: | Field | What it holds | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `category` | Class, severity, description, remediation, links. | | `hash` | Stable identity of the finding across scans. | | `sources` / `sink` | Taint flow — origin nodes and the sink (`{code, file?, line}`). Empty/null for config-only findings. | | `code` | SAST code snippets — decompiled files with the matched lines flagged. | | `stackTraces` | DAST runtime stack traces with a heap snapshot of the variables. | | `pocs` | Proofs of concept (e.g. `kind: "shell"` — an adb command). | | `llmDescription` | AI explanation: impact, how-to-fix steps, CVSS-style vector, tags. | | `attachments` | Files attached to the finding (e.g. an exploit demo video). | | `chainType` / `codeOrigin` / `thirdPartyLabel` | Chain membership and whether the code is the app's own (`custom`) or a third-party SDK. | | `folderId` | Current triage folder. | A real response «Theft of arbitrary files via XHR requests in a WebView»: ```json { "id": "4ac9cb0f-d32f-4462-8a9e-fb3660fc4949", "scanId": "9e9086c7-8e09-4ca8-b764-a8cb19666706", "categoryId": "B26FB3AB-11C2-4194-99BD-F5DAC384AC35", "hash": "ecd499a893859f5cd43976eb854f89cf919b059b020deccdce4f6add817946eb", "chainType": null, "codeOrigin": "custom", "thirdPartyLabel": null, "folderId": null, "sources": [], "sink": null, "code": [ { "file": "AndroidManifest.xml", "source_type": "xml", "lines": [ { "line_number": 12, "is_matched": true, "content": " " } ] } ], "stackTraces": [ { "thread_name": "main", "frames": [ { "element": { "className": "android.webkit.WebView", "methodName": "loadUrl", "fileName": "WebView.java", "lineNumber": 743 }, "variables": [ { "is_field": true, "name": "this", "class_name": "android.webkit.WebView", "value_id": "81442C35-CB62-4DD2-844B-1B1D96854B23" } ] } ], "values": [ { "id": "A0D0464D-…", "class_name": "float", "value_type": "primitive", "value_content": { "display_value": "NaN" } } ], "highlights": [{ "frame_index": 1, "path": [{ "type": "field", "name": "this" }] }] } ], "pocs": [ { "kind": "shell", "code": [ { "source_type": "shell", "lines": [ { "line_number": 1, "is_matched": false, "content": "adb shell am start -n oversecured.ovaa/oversecured.ovaa.activities.DeeplinkActivity …" } ] } ] } ], "llmDescription": { "summary": "The app exposes a maliciously crafted deep link that triggers a WebView to load…", "businessImpact": "Attackers can steal sensitive local files including authentication tokens…", "technicalImpact": "An attacker can craft a deep link that loads a malicious webpage into the WebView…", "technicalDetailsProse": "The DeeplinkActivity is exported and accepts arbitrary URIs…", "howToFix": [ { "title": "Disable exported deep link unless absolutely necessary", "details": ["Set android:exported=\"false\" on DeeplinkActivity…"] } ], "technicalDetails": { "attackVector": "network", "attackLocality": "remote", "privilegesRequired": "none", "userInteraction": "none", "scope": "unchanged", "confidentialityImpact": "high", "integrityImpact": "none", "availabilityImpact": "none" }, "exploitability": { "difficulty": "trivial", "requiredKnowledge": "Basic Android deep link exploitation…" }, "remediation": "Disable exported deep link and disable file access from file URLs in WebView.", "tags": ["deep-link", "webview"] }, "attachments": [ { "id": "attachments/demo.mp4", "fileName": "demo.mp4", "attachmentType": "video", "size": null, "url": "https://…s3…/attachments/demo.mp4" } ], "category": { "id": "b26fb3ab-11c2-4194-99bd-f5dac384ac35", "severity": "high", "platform": "android", "descriptionTitle": "Theft of arbitrary files via XHR requests in a WebView", "descriptionText": "An attacker can steal files through a WebView because…", "descriptionRemediation": "The developer should immediately remove the file access permission…", "descriptionLinks": [ { "Url": "https://blog.oversecured.com/Android-security-checklist-webview/…" } ] }, "createdAt": "2026-07-21T14:03:42.364Z", "updatedAt": "2026-07-21T14:03:42.364Z" } ``` This finding is config-only, so `sources`/`sink` are empty. For taint-flow findings they look like this: ```json { "sources": [ { "code": "\"example.com\"", "file": "oversecured/ovaa/activities/DeeplinkActivity.java", "line": 45 }, { "code": "android.app.Activity.__getIntentHooked()", "line": 146 } ], "sink": { "code": "java.lang.String.endsWith(...)", "file": "oversecured/ovaa/activities/DeeplinkActivity.java", "line": 45 } } ``` Framework-level nodes omit `file`. Structure notes: - `code[]` — one entry per decompiled file: `file`, `source_type` (`java`, `xml`, `shell`, …), `lines[]` with `line_number` / `content` / `is_matched` (`true` marks the lines the finding points at). - `stackTraces[]` — captured at the moment the vulnerable call executed: `frames[]` (call stack, sink first; each frame has `element` and `variables[]` whose `value_id` points into `values[]`), `values[]` (deduplicated heap snapshot), `highlights[]` (which frame/variable chain is attacker-controlled). - `pocs[]` — `kind` (e.g. `shell`) + `code[]` in the same snippet format. - `llmDescription` — `summary`, `businessImpact`, `technicalImpact`, `technicalDetailsProse`, `howToFix[]` (`title` + `details[]`), `technicalDetails` (CVSS-style vector, lowercase), `exploitability` (`difficulty`, `requiredKnowledge`), `remediation`, `tags[]`. - `attachments[]` — `id` (pass to the download-url endpoint), `fileName`, `attachmentType` (`video`, `image`, …), `size`, `url`. ## Export the report ``` GET …/export?format=pdf ``` `format` is one of `pdf`, `json`, `csv`, `md`. `pdf` and `csv` return binary; `json` and `md` return text. Add `pretty=true` (JSON only) to pretty-print with 2-space indent. ```bash curl -fsS "$BASE/workspaces/$WS/apps/$APP/scans/$SCAN/export?format=pdf" \ -H "x-api-token: $OS_TOKEN" -o report.pdf ``` ## Export a single finding ``` GET …/vulnerabilities/{vulnId}/export?format=md ``` Same formats. Useful for attaching one finding to a ticket. ## Download an attachment ``` GET …/attachments/{attachmentId}/download-url ``` Returns a presigned URL — `GET` it directly to fetch the file: ```json { "downloadUrl": "https://s3…", "fileName": "screenshot.png", "contentType": "image/png" } ``` See [Exporting reports](/guide-exporting-reports) for the archival/SIEM patterns. --- # API — Triage (/docs/api-triage) Folders and move / mark-false-positive. Organize findings into folders and mark false positives programmatically. Mirrors the dashboard's triage actions. Each scan has system folders (including **False positives**) plus any custom folders you create. All paths are under `…/workspaces/{workspaceId}/apps/{appId}/scans/{scanId}`. ## List folders ``` GET …/folders ``` Returns `{ "items": [Folder], "total": N }`. Each `Folder` has `id`, `name`, and `kind` — `custom` or `false_positives`. System folders are locked. ## Create a folder ``` POST …/folders ``` ```json { "name": "Accepted risk" } ``` Returns `201` with the created `Folder`. ## Rename a folder ``` PATCH …/folders/{folderId} ``` ```json { "name": "Accepted risk (Q3)" } ``` Only custom folders. Renaming a system folder is rejected. ## Delete a folder ``` DELETE …/folders/{folderId} ``` Returns `204`. System folders (e.g. **False positives**) are locked and can't be deleted. Findings in a deleted folder become unassigned. ## Move findings / mark false positives ``` POST …/move-vulnerabilities ``` One call moves a batch of findings. `destination` is a folder UUID, the literal `false_positives`, or `unassigned`: ```json { "destination": "false_positives", "vulnIds": ["b1c2d3e4-1111-4222-8333-444455556666"] } ``` | `destination` | Effect | |---|---| | `` | Move into that custom folder. | | `false_positives` | Mark as false positive (the system folder). | | `unassigned` | Clear the folder. | Returns `{ "moved": N, "folderId": "…" }`. There's no separate "mark false positive" endpoint — a false positive *is* a move into the False positives folder. For the triage workflow and how moved findings interact with a CI gate, see [Triaging findings](/guide-triaging-findings). --- # API — Webhooks (/docs/api-webhooks) Events, payload, signature, test endpoint. The recommended way to learn a scan finished — subscribe instead of polling. Up to **5 webhooks per app**. Note the path segment is `applications` (not `apps`). ## Register a webhook ``` POST /workspaces/{workspaceId}/applications/{appId}/webhooks ``` ```json { "url": "https://hooks.example.com/oversecured" } ``` Only `https://` URLs are accepted. The response includes the signing **`secret`** — store it; it's used to verify the `x-webhook-signature` header on every delivery, and it isn't shown again. ```json { "id": "…", "url": "https://hooks.example.com/oversecured", "secret": "3f9a…(64-char hex)", "createdAt": "…" } ``` ## List webhooks ``` GET /workspaces/{workspaceId}/applications/{appId}/webhooks ``` Returns the app's webhooks (without secrets). To list across every app in the workspace: `GET /workspaces/{workspaceId}/webhooks`. ## Update a webhook ``` PUT /workspaces/{workspaceId}/applications/{appId}/webhooks/{webhookId} ``` Same body as create. Returns the updated `Webhook`. ## Delete a webhook ``` DELETE /workspaces/{workspaceId}/applications/{appId}/webhooks/{webhookId} ``` Returns `204`. ## Rotate the secret ``` POST /workspaces/{workspaceId}/applications/{appId}/webhooks/{webhookId}/regenerate-secret ``` Issues a new signing secret and returns it. The old secret stops validating immediately — update your receiver in the same change. ## Test a webhook ``` POST /workspaces/{workspaceId}/applications/{appId}/webhooks/{webhookId}/test ``` Sends a synthetic `test` event to the URL synchronously (10s timeout) and reports the delivery result. Use it to verify connectivity and signature handling. ## Event payload Oversecured `POST`s a JSON body when an app version is added or deleted, or when a scan starts, finishes, or errors. **Events** (`action`): `version_added`, `version_deleted`, `version_status_scanning`, `version_status_finish`, `version_status_error`, `test`. A `version_status_finish` delivery — a scan completed: ```json { "action": "version_status_finish", "integration": { "id": "a1b2c3d4-…", "name": "Acme Banking", "platform": "android", "app_id": "com.acme.banking", "smart_filter_enabled": true, "notify_when_done": true }, "branch_name": "release/4.2.0", "version": { "id": "8a1f0c2e-…", "file_name": "app-release.apk", "platform": "android", "version_id": "4.2.0", "status": "FINISH", "version_compare": { "high": { "fixed": 1, "created": 0 }, "medium": { "fixed": 2, "created": 3 }, "low": { "fixed": 0, "created": 5 } }, "completed_at": "2026-06-03T10:22:31.000Z" } } ``` `version.id` is the scan/version identifier; `version_compare` is the per-severity delta vs the previous version (`created` = new findings, `fixed` = gone). Return any `2xx` to acknowledge — non-2xx and timeouts are retried with exponential backoff. ## Verifying signatures Every delivery carries: | Header | Value | |---|---| | `Content-Type` | `application/json` | | `x-webhook-signature` | `sha256=` — HMAC-SHA256, keyed with the webhook `secret`, over the string `{timestamp}.{rawBody}`. | | `x-webhook-timestamp` | Delivery time in **milliseconds** since the epoch. It's part of the signed message, so a receiver can reject stale deliveries. | The signed message is the timestamp header, a literal `.`, then the raw request body (not a re-serialized object). Verify in constant time: ```js import crypto from 'node:crypto'; function verify(rawBody, signatureHeader, timestampHeader, secret) { const signed = `${timestampHeader}.${rawBody}`; const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(signed).digest('hex'); return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected)); } ``` See [Waiting for results](/guide-waiting-for-results) for webhooks vs polling. --- # CLI — Overview (/docs/cli-overview) Install, authenticate, configure the CLI. `@oversecured/cli` wraps the API so a scan is one command — no hand-rolling the REST calls. Used in clients' CI/CD pipelines, the Oversecured MCP, and the GitHub/GitLab integrations. One command does the whole upload→version→scan dance; you only ever pass the **app id**, and the workspace is resolved from your token: ```bash oversecured scan path/to/app.apk 93c92471-fa34-478f-99b4-6def7ff7fea9 ``` > Beta: the current published version is `0.1.0-beta.x`. Pin a version in CI (`@oversecured/cli@0.1.0-beta.3`) if you need reproducible builds. ## Install The package is published on npm: **[npmjs.com/package/@oversecured/cli](https://www.npmjs.com/package/@oversecured/cli)**. ```bash npm i -g @oversecured/cli # global — provides the `oversecured` command npx @oversecured/cli # or run without installing ``` Requires **Node.js ≥ 20**. ## Authenticate Create a workspace API token in the dashboard (**Workspace settings → API keys**), then provide it — highest precedence first: `--token` → `OVERSECURED_API_TOKEN` → config file. ```bash export OVERSECURED_API_TOKEN=osp_xxxxxxxx # recommended for CI (store as a secret) oversecured login # interactive — local machines oversecured config set token osp_xxxxxxxx # persist to ~/.oversecured/config.json ``` The token is already scoped to one workspace, so you never pass a workspace id — the CLI resolves and caches it. Run `oversecured apps` to find an app id. ## Configure | Env var | Purpose | |---|---| | `OVERSECURED_API_TOKEN` | API token. | | `OVERSECURED_API_URL` | API base URL. Default `https://api.platform.oversecured.com`; for an on-prem appliance use its API origin, e.g. `https://platform.example.internal/api` (or `http://localhost:3000` in local dev). | | `OVERSECURED_WORKSPACE_ID` | Override the workspace (normally auto-resolved). | | `NO_COLOR` | Disable ANSI colors. | Persistent config lives in `~/.oversecured/config.json` (written by `oversecured config set …`). Global flags `--token`, `--api-url`, `--workspace`, `--json`, `--no-color`, `-v/--verbose` override env and config per-invocation. With `--json`, every command prints a stable envelope on **stdout** while progress/diagnostics go to **stderr** — ideal for MCP and scripting: ```json { "ok": true, "command": "scan", "data": { "id": "…", "status": "queued" } } ``` See [Commands](/cli-commands) for the full reference and [CI/CD](/cli-ci-cd) for pipeline recipes. --- # CLI — Commands (/docs/cli-commands) Full command reference. Command reference. Run `oversecured --help` for the full option list. Install from npm — **[npmjs.com/package/@oversecured/cli](https://www.npmjs.com/package/@oversecured/cli)** — or see [CLI — Overview](./cli-overview.md) for setup and authentication: ```bash npm i -g @oversecured/cli # global — provides the `oversecured` command npx @oversecured/cli # or run without installing ``` | Command | Description | | ------------------------------------------------ | ------------------------------------------------------------------ | | `scan [--tag ]` | Upload a `binary`, create the `version`, start a `scan`. | | `scans [--tag ]` | List scans, optionally filtered by report tags. | | `status --app ` | Fetch the current status of a `scan`. | | `scan-tags --app ` | List tags assigned to a scan report. | | `set-scan-tags --app [--tag ]` | Replace all tags assigned to a scan report. | | `workspace-tags` | List report tags used in the current workspace. | | `report --app ` | Download a `report`. | | `findings --app ` | List `findings` with filters. | | `finding --scan --app ` | Fetch one `finding` with all fields, or export it with `--format`. | | `apps` | List the workspace's `apps`. | | `create-app --platform

` | Create a new `app`. | | `redeem-coupon ` | Credit a coupon's `scan` quota to the workspace. | | `config` / `login` | Manage `token` / `API URL` / `workspace`. | Global options on every command: `--token`, `--api-url`, `--workspace`, `--json`, `--no-color`, `-v/--verbose`. ## `scan` — add a version and start a scan ```bash oversecured scan path/to/app.apk 93c92471-fa34-478f-99b4-6def7ff7fea9 ``` Obtains an `uploadUrl`, uploads the `binary`, creates the `version` parsed from the file, and starts the `scan`. By default it prints the `scanId` and exits `0`. Add `--wait` to block until the `scan` finishes, and `--fail-on` to gate a build: ```bash oversecured scan app.apk "$APP_ID" --wait --fail-on high ``` Run static analysis only and skip dynamic analysis (DAST) for this scan: ```bash oversecured scan app.apk "$APP_ID" --skip-dast ``` Assign report tags while starting a scan. Surrounding whitespace is trimmed and repeated spaces are collapsed. Letter case, spaces, and hyphens are otherwise preserved: ```bash oversecured scan app.apk "$APP_ID" --tag release --tag "Mobile Team" ``` | Flag | Default | Meaning | | --------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------- | | `--wait` | off | Block until the `scan` reaches a terminal state. | | `--fail-on ` | — | Exit non-zero if a `finding` at/above this severity is reported (implies `--wait`). | | `--skip-dast` | off | Run static analysis only — skip `DAST` for this `scan`. Without it, the app-level `DAST` setting applies. | | `--mock` | off | Synthetic `scan`, no real upload. | | `--tag ` | — | Assign a report tag. Repeat the flag to assign multiple tags. | | `--platform ` | inferred from extension | Force the `platform`. | | `--timeout ` | `1800` | Max wait when `--wait`. | | `--poll-interval ` | `5` | Status poll cadence when `--wait`. | ## `status` — get scan status ```bash oversecured status --app ``` Returns the current scan state. Add `--json` when polling from scripts or MCP clients. ## `report` — download the report ```bash oversecured report --app --format [--output report.pdf] ``` Without `--output`, text formats print to `stdout` (`--output -` forces `stdout`). `pdf`/`csv` are binary, so give an `--output` path. `--pretty` indents `json` output. ## Report tags ```bash oversecured scans "$APP_ID" --tag release --tag mobile oversecured scan-tags "$SCAN_ID" --app "$APP_ID" oversecured set-scan-tags "$SCAN_ID" --app "$APP_ID" --tag release --tag mobile oversecured workspace-tags ``` Multiple `--tag` filters on `scans` match reports containing any supplied tag. Repeat `--tag` to assign or filter by multiple tags. `set-scan-tags` replaces the complete tag list; omit `--tag` to clear all tags from the report. A report can have up to 20 tags, with up to 100 characters per tag. ## `findings` — list findings with filters ```bash oversecured findings \ --app \ [--severity high,medium] \ [--category ] \ [--folder ] \ [--page ] \ [--page-size ] \ [--json] ``` Filters mirror the API: `--severity` (CSV), `--category` (CSV of category UUIDs), `--folder` (folder id). Paginate with `--page` (default `1`) and `--page-size` (default `20`, max `100`). Add `--json` for the machine envelope (`.data.items` / `.data.total`). ## `finding` — get or export one finding ```bash oversecured finding --scan --app [--json] oversecured finding --scan --app --format md --output finding.md ``` Without `--format`, returns the full vulnerability (category, evidence, AI description, attachments). With `--format ` it exports a formatted document; `--output ` (or `-` for stdout) sets the destination, and `--pretty` indents JSON. ## `apps` — list apps ```bash oversecured apps [--json] ``` Lists the workspace's apps so you can look up an `appId`. ## `create-app` — create a new app ```bash oversecured create-app "Acme Banking" --platform android [--package-name com.acme.banking] [--description "…"] ``` The token needs the **Applications → Create** permission. ## `redeem-coupon` — add scan quota ```bash oversecured redeem-coupon ``` Redeems a coupon by code, crediting its scan quota to your workspace. ## `config` / `login` — manage credentials ```bash oversecured login # interactive token entry (local machines) oversecured config set token osp_xxxx # persist to ~/.oversecured/config.json oversecured config set api-url http://localhost:3000 ``` See [CI/CD](/cli-ci-cd) for pipeline recipes and exit-code handling. --- # CLI — CI/CD reference (/docs/cli-ci-cd) CLI exit codes and gating flags. Exit codes and flags for running `oversecured scan` in a pipeline. For copy-paste GitHub Actions / GitLab configs and the how-to, see the [CI/CD integration guide](/guide-ci-cd). ## Gating flags `oversecured scan … --wait --fail-on ` blocks until the scan finishes, then sets the exit code from the outcome. | Flag | Default | Meaning | |---|---|---| | `--wait` | off | Block until the scan reaches a terminal state. | | `--fail-on ` | — | Exit non-zero if a finding at/above this severity (`high`/`medium`/`low`) is reported. Implies `--wait`. | | `--timeout ` | `1800` (30 min) | Max wait when `--wait`. Raise for long Android scans (e.g. `14400` = 4h). | | `--poll-interval ` | `5` | Status poll cadence when `--wait`. | | `--json` | off | Emit the stable result envelope on stdout (progress goes to stderr). | ## Exit codes | Code | Meaning | |---|---| | `0` | Success — no finding met the `--fail-on` threshold. | | `2` | Usage / validation error. | | `3` | Authentication error (token invalid / revoked / expired). | | `4` | Forbidden / not found. | | `5` | Scan ended in a non-`completed` state, **or** the `--fail-on` threshold was met. | | `6` | Network / timeout. | | `7` | Insufficient scan balance (`HTTP 402`) — top up before scanning. | | `1` | Other error. | Most pipelines fail on `--fail-on high`. `--fail-on` is severity-only — it counts every finding at/above the level, including ones triaged as false positives. To count only runtime-confirmed (DAST) findings or exclude false positives, build the check yourself — see the [CI/CD integration guide](/guide-ci-cd#custom-conditions-optional). --- # Connect an AI assistant (/docs/mcp-overview) Use Oversecured from Codex, Claude, Cursor, and other MCP-compatible AI assistants. The Oversecured MCP server lets an AI assistant list your apps, start scans, check their status, and retrieve reports and findings. You do not need to install the Oversecured CLI or host the MCP server. Your AI client starts the published package locally through `npx`. ## Before you start You need: - Node.js 20 or newer; - an MCP client with local stdio server support; - a workspace API token created under **Workspace settings → API tokens**. Keep the token in the MCP server environment. Do not paste it into prompts, commit it to a repository, or include it in screenshots. ## Install Choose your AI client and add the Oversecured MCP server. 1. Open **Settings → MCP servers**. 2. Select **Add server** and choose a local stdio server. 3. Set **Command to launch** to `npx`. 4. Add two separate arguments: `-y` and `@oversecured/mcp@latest`. 5. Add `OVERSECURED_API_TOKEN` under **Environment variables**. 6. Leave **Working directory** empty, or select a directory that exists. 7. Save the server, enable it, and open a new task. Do not put the entire `npx -y @oversecured/mcp@latest` string into the command field. `npx` is the command; the other two values are arguments. Open **Settings → Developer → Edit Config** and add: ```json { "mcpServers": { "oversecured": { "command": "npx", "args": ["-y", "@oversecured/mcp@latest"], "env": { "OVERSECURED_API_TOKEN": "osp_xxxxxxxx" } } } } ``` Save the file and restart Claude Desktop completely. Open **Cursor Settings → MCP**, add a new global MCP server, and use: ```json { "mcpServers": { "oversecured": { "command": "npx", "args": ["-y", "@oversecured/mcp@latest"], "env": { "OVERSECURED_API_TOKEN": "osp_xxxxxxxx" } } } } ``` Save the configuration, enable the server, and open a new chat. Use the client's local stdio server configuration: ```json { "mcpServers": { "oversecured": { "command": "npx", "args": ["-y", "@oversecured/mcp@latest"], "env": { "OVERSECURED_API_TOKEN": "osp_xxxxxxxx" } } } } ``` If the client uses separate form fields, enter `npx` as the command and add `-y` and `@oversecured/mcp@latest` as separate arguments. On Windows, the command may be `npx.cmd`. For an on-prem installation, add the platform API origin to the same environment block: ```json { "env": { "OVERSECURED_API_TOKEN": "osp_xxxxxxxx", "OVERSECURED_API_URL": "https://platform.example.internal/api" } } ``` The package contains the matching Oversecured CLI binary, so no separate CLI installation or configuration is required. ## Verify the connection Restart the client, open a new chat or task, and ask: > Show my Oversecured applications. The assistant should call `list_apps` and return applications from the workspace associated with the token. If it runs shell commands or sends JSON-RPC manually instead, the MCP server is not connected correctly. You can then try a complete flow: > Start a SAST-only scan of `/absolute/path/app.apk`, wait for it to finish, and > show me the findings. Use an absolute file path visible to the machine running the MCP client. ## Available tools | Tool | Description | | --------------------- | ---------------------------------------------------------------------- | | `list_apps` | List apps in the token's workspace. | | `create_app` | Create an Android or iOS app. | | `start_scan` | Upload a binary and start a scan. | | `list_scans` | List scans, optionally filtered by report tags. | | `get_scan_status` | Read the current scan state and progress. | | `get_scan_tags` | List tags assigned to a scan report. | | `set_scan_tags` | Replace all tags assigned to a scan report. | | `list_workspace_tags` | List report tags used in the token's workspace. | | `get_report` | Download a report to a local file. | | `list_findings` | List findings with severity, category, folder, and pagination filters. | | `get_finding` | Read or export one finding. | | `redeem_coupon` | Redeem scan quota. | Report tags preserve letter case, spaces, and hyphens. The platform only trims surrounding whitespace and collapses repeated spaces. `set_scan_tags` replaces the complete tag list; pass an empty list to clear it. `start_scan` returns as soon as the scan is queued unless `wait` is `true`. For a long-running scan, the assistant can check `get_scan_status` until the scan finishes, then retrieve its report or findings. Downloaded reports are written to a unique directory under the operating system's temporary directory. The tool returns the absolute path, and the temporary output is removed automatically after one hour. ## Authentication The MCP server reads credentials only from its process environment and ignores the local CLI configuration file: | Variable | Purpose | | -------------------------- | -------------------------------------------------------------- | | `OVERSECURED_API_TOKEN` | Workspace API token. | | `OVERSECURED_API_URL` | Optional API override for on-prem or local development. | | `OVERSECURED_WORKSPACE_ID` | Optional workspace override; normally resolved from the token. | Tokens are not accepted as tool arguments and are not returned in tool results. Create a dedicated workspace API token and grant only the permissions required for the operations you plan to use. ## Troubleshooting ### The server is enabled, but Oversecured tools are missing - Open a new chat or task after enabling the server. Existing conversations may keep the tool list they started with. - Confirm that the working directory is empty or points to a directory that exists. - Confirm that `npx -y @oversecured/mcp@latest` starts in a terminal. ### The client cannot find `npx` GUI applications may not inherit the shell environment when Node.js was installed through `nvm`. On macOS or Linux, configure the server to start through the shell instead: ```json { "command": "/bin/zsh", "args": [ "-lc", "source \"$HOME/.nvm/nvm.sh\" && nvm use --silent 20 && exec npx -y @oversecured/mcp@latest" ] } ``` Replace `20` with another installed Node.js version if needed. ### Authentication or API errors - **`NO_TOKEN`:** add `OVERSECURED_API_TOKEN` to the MCP server environment and restart the client. - **Authentication error:** verify that the token is active and belongs to the expected workspace. - **App forbidden or not found:** list apps first and use an app ID available to the token. - **On-prem connection failure:** set `OVERSECURED_API_URL` to the platform API origin. If a token was exposed in a prompt, terminal output, or screenshot, revoke it and create a new one.