Source Code API Discovery
Levo's Source Code API Discovery catalogs every REST endpoint defined in a repository — handlers, paths, methods, path and query parameters, request and response types — and imports the result into Levo.ai. It runs entirely against a source checkout: the application is not executed, no traffic is captured, and no staging environment is required.
This is the fastest path to a complete API inventory for repositories you already govern in source control. Two modes are supported and can be combined on the same repository:
- Source-code scan — statically analyzes application code and generates a new OpenAPI 3.0.1 specification.
- Specification import — locates existing OpenAPI or Swagger specification files (
openapi.yaml,openapi.yml,swagger.json, etc.) in the repository and imports them as-is, preserving the original OpenAPI or Swagger version.
How it works
- The Levo scanner runs locally as a container — on a developer workstation, a CI runner, or any host with Docker — against a repository checkout you control.
- Depending on the mode, the scanner prepares an API specification for upload in one of two ways:
- Source-code scan: static analysis extracts route definitions, handler bindings, annotations, and parameter schemas directly from the source tree and generates an OpenAPI 3.0.1 specification. The application is never compiled-and-run or otherwise executed against live traffic.
- Specification import: the scanner locates existing
.yaml,.yml, or.jsonfiles identified as OpenAPI or Swagger specifications in the repository and uses them as-is. No static analysis is performed, and the specification's original format (OpenAPI 3.x or Swagger 2.0) is preserved.
- The specification is transmitted over TLS to your Levo tenant at https://api.levo.ai.
- Levo validates the specification server-side and imports it into the application and environment you specify. The application record is created automatically on first run.
- Endpoints become available in the Levo dashboard for API inventory, risk scoring, and security testing.
Source code never leaves the host executing the scanner. Only the generated or discovered API specification itself is uploaded to https://api.levo.ai.
Supported languages and frameworks
| Language | File Markers | Notes |
|---|---|---|
| Java | .java, pom.xml, build.gradle | Requires compilation |
| JAR | .jar files | Pre-compiled Java archives |
| JavaScript | .js, package.json | — |
| TypeScript | .ts, tsconfig.json | — |
| Python | .py, requirements.txt, setup.py, pyproject.toml | Supports 3.x through 3.13 |
| C / C++ | .c, .cpp, .h | Includes .h headers and pre-processed .i files |
| PHP | .php, composer.json | Runtime PHP ≥ 7.4 required. Source compatibility: PHP 7.0 – 8.4 |
| Ruby | .rb, Gemfile | Runtime Ruby 4.0.x required. Source compatibility: Ruby 1.8 – 4.0.x |
| C# | .cs, .csproj | Supports C# code targeting .NET Framework 4.x through .NET 9. Pass --language csharp. Uses a Roslyn-based static analyzer bundled in the container image — no application build, runtime, or environment configuration required. VB.NET and F# are not supported. |
| Android APK | .apk files | Requires the Android SDK. Set the ANDROID_HOME environment variable, or use the container image. |
| Scala | .scala, build.sbt | Work in progress |
Prerequisites
- Docker is installed on the host and you can launch containers with outbound network connectivity.
- https://api.levo.ai is reachable from the host that will run the scan.
- Your CLI Authorization Key — get it from app.levo.ai/settings/keys.
- Your Organization ID — get it from app.levo.ai/settings/organization.
Get your CLI authorization key from app.india-1.levo.ai/settings/keys and your organization ID from app.india-1.levo.ai/settings/organization instead.
Approach-specific prerequisites are listed under each approach below.
Integration approaches
Source Code API Discovery can be consumed in one of the following ways today. All approaches run the same underlying scanner (published as the levoai/code-scanner container image) and upload the specification to the same Levo tenant — a generated OpenAPI 3.0.1 spec for source-code scans, or the existing OpenAPI/Swagger spec as-is for specification imports. Additional integrations are on the roadmap.
| Approach | Best suited for |
|---|---|
| Docker | Interactive scans from a developer workstation, scheduled scans in any CI system (Jenkins, GitLab CI, Bitbucket Pipelines, CircleCI, Azure Pipelines, etc.), and ad-hoc runs inside a container. |
| GitHub Actions or Azure Pipelines | Declarative, automated scans triggered on every push or pull request, for repositories hosted on GitHub or Azure Repos. |
| Bulk Scanning Script | Generating OpenAPI specifications for every repository in a GitHub organization in one batch run. Useful for initial API inventory across an entire engineering org, security backfills, or onboarding a new tenant. |
| GitHub App | Continuous, scheduled scans of every repository in a GitHub organization from one control repository, without adding a workflow to each repository. Scanning runs on your own GitHub runners with a GitHub App you create — Levo never receives a credential that can read your source. |
Choose based on where your repositories live and how you want scans triggered. You can adopt multiple approaches in the same organization — for example, Docker for ad-hoc backfills and GitHub Actions or Azure Pipelines for continuous coverage.
Approach 1: Docker
Run the scanner locally or in any CI system as a Docker container. No Levo CLI installation is required; everything the scanner needs ships inside the image.
Mode A: Scan source code and discover REST endpoints
Use this mode to reverse-engineer an OpenAPI specification from application source.
First, pull the latest image:
docker pull levoai/code-scanner:latest
Then navigate to the project directory you want to scan. Set --language to match your project's programming language (e.g., java, python, javascript, typescript, c, cpp, php, ruby, csharp, dotnet):
cd /path/to/your/project
docker run --rm \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
--app-name "my-api" \
--env-name "staging" \
--language <your-project-language> \
--key <your-authorization-key>
On success, you'll see a confirmation that the application has been created — check your Levo dashboard.
Set -e LEVO_BASE_URL=https://api.india-1.levo.ai instead.
If you belong to multiple organizations, add the -o flag:
docker run --rm \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
--app-name "my-api" \
--env-name "staging" \
--language <your-project-language> \
--key <your-authorization-key> \
-o <your-organization-id>
Flags Reference
| Flag / Env Var | Required | Description |
|---|---|---|
-e LEVO_BASE_URL | No | Levo SaaS API URL. Default: https://api.levo.ai. India: https://api.india-1.levo.ai. |
-v "$(pwd)":/workspace:rw | Yes | Mounts your source code into the container. |
--app-name | Yes | Application name on the Levo dashboard. Created automatically on first scan. |
--env-name | Yes | Environment label (e.g., staging, production). |
--language | Yes | Source language: java, python, javascript, typescript, c, cpp, php, ruby, csharp. |
--key | Yes | Your CLI authorization key. |
-o | No | Organization ID. Required only if you belong to multiple organizations. |
--include-dirs | No | Comma- or space-separated list of subdirectories to scan (whitelist). When set, only these paths are analyzed. Useful for narrowing scans in large monorepos. |
--exclude-dirs | No | Comma- or space-separated list of subdirectories to skip (blacklist). The full source minus these paths is scanned. Can be combined with --include-dirs to narrow further. |
--auto-split | No | Scans a large monorepo folder-by-folder instead of all at once. Useful when a whole-repo scan times out. |
--mandatory-scan | No | Comma-separated list of folders that must be scanned even if their scan times out. Only effective together with --auto-split. |
--per-folder-timeout | No | Per-folder timeout in seconds when --auto-split is set. Default: 600 (10 minutes). |
Scanning large monorepos with --include-dirs / --exclude-dirs
For large monorepos, you often want to scan only the services that expose APIs, or skip directories that slow the scan down without adding value (test fixtures, vendored dependencies, generated code). Two flags control this:
--include-dirs— whitelist. Only the listed subdirectories are scanned. Everything else in the repository is ignored.--exclude-dirs— blacklist. The full repository is scanned except the listed subdirectories.
Both accept a comma- or space-separated list of paths relative to the mounted project root. Paths must stay inside the project — absolute paths and .. traversal are rejected. The two flags can be combined: includes are applied first, then excludes carve out subpaths from within the included set.
Example — scan only two services in a monorepo (--include-dirs):
docker run --rm \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
--app-name "my-api" \
--env-name "staging" \
--language java \
--key <your-authorization-key> \
--include-dirs "services/payments,services/orders"
Example — scan everything except tests and vendored code (--exclude-dirs):
docker run --rm \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
--app-name "my-api" \
--env-name "staging" \
--language java \
--key <your-authorization-key> \
--exclude-dirs "tests,vendor,third_party"
Entries that don't exist in the repository are reported as warnings and skipped — the scan still runs. Common noise directories (.git, node_modules, __pycache__, .idea, .vscode) are always skipped regardless of these flags.
Scanning very large monorepos with --auto-split / --mandatory-scan
If a whole-repo scan times out on a very large monorepo, --auto-split scans it folder-by-folder instead of all at once. Add --mandatory-scan to name folders that must produce results even if their scan times out.
Example — auto-split with mandatory folders:
docker run --rm \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
--app-name "my-api" \
--env-name "staging" \
--language java \
--key <your-authorization-key> \
--auto-split \
--mandatory-scan "services/payments,services/orders"
Mode B: Import existing OpenAPI / Swagger specifications
Use this mode when your repositories already contain hand-authored or generated OpenAPI specifications (.yaml, .yml, or .json) and you want to import them directly, without running static analysis.
cd /path/to/your/project
docker run --rm \
--entrypoint levo-code-scanner \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
schema \
--dir . \
--env-name "staging" \
--key <your-authorization-key>
Flags Reference
| Flag / Env Var | Required | Description |
|---|---|---|
--entrypoint levo-code-scanner | Yes | Runs the container in spec-import mode instead of the default source-scan mode. |
-e LEVO_BASE_URL | No | Levo SaaS API URL. Default: https://api.levo.ai. India: https://api.india-1.levo.ai. |
-v "$(pwd)":/workspace:rw | Yes | Mounts your specs directory into the container. |
--dir | Yes | Directory to scan for OpenAPI/Swagger spec files. |
--env-name | Yes | Environment label (e.g., staging, production). |
--key | Yes | Your CLI authorization key. |
-o | No | Organization ID. Required only if you belong to multiple organizations. |
--spec-file | No | Import a single spec file instead of scanning all of --dir. Accepts a filename or a path relative to --dir. |
--app-name | No | Override the application name for the imported spec. Only valid together with --spec-file. |
Notes:
- The scanner recursively searches the mounted directory for OpenAPI/Swagger spec files; non-spec files are skipped.
- The application name for each imported specification is taken from the
info.titlefield of that specification, unless overridden with--app-name. - Use
--dir <relative-path>to scan only a sub-directory of the project.
Example — import a single spec file:
docker run --rm \
--entrypoint levo-code-scanner \
-e LEVO_BASE_URL=https://api.levo.ai \
-v "$(pwd)":/workspace:rw \
levoai/code-scanner:latest \
schema \
--dir . \
--spec-file openapi.yaml \
--app-name "my-api" \
--env-name "staging" \
--key <your-authorization-key>
Approach 2: GitHub Actions or Azure Pipelines
Automate code scanning in your CI/CD pipeline. Every push to the configured branches triggers a scan, and discovered API endpoints are uploaded to the Levo dashboard automatically. Two runners are supported — both invoke the same levoai/code-scanner container image and support the same languages as the Docker approach (see Supported languages and frameworks):
- GitHub Actions — for repositories hosted on GitHub.
- Azure Pipelines — for repositories hosted on Azure Repos.
Prerequisites
- A Levo.ai account.
- A repository on GitHub or Azure Repos, with source code in a supported language.
- Permission to add GitHub Actions secrets or Azure pipeline variables to that repository (typically: repository admin).
That's it. You do not need to install Docker, the Levo CLI, or anything else on your local machine — the runner / pipeline agent runs everything.
Levo runs two production tenants. Use the URLs that match the tenant you log in to:
| U.S. customers | Indian customers | |
|---|---|---|
| Dashboard | app.levo.ai | app.india-1.levo.ai |
| Get the auth key | app.levo.ai/settings/keys | app.india-1.levo.ai/settings/keys |
| Get the org ID | app.levo.ai/settings/organization | app.india-1.levo.ai/settings/organization |
API endpoint (saas-url) | https://api.levo.ai (used automatically — no action needed) | https://api.india-1.levo.ai (you must add this to the workflow — see Step 2) |
If you log in at app.india-1.levo.ai, you are an Indian-tenant customer and must follow the Indian-customer notes in every step below. Skipping the saas-url value will send your scan results to the wrong tenant.
Setup
The setup is three short steps:
- Add two secrets to your repository (your Levo auth key and your Levo org ID).
- Create one workflow file.
- Commit and push.
Step 1 — Add two secrets to your GitHub repository
These two secrets tell the Action who you are and which Levo organization to upload to. Without them the scan cannot run.
In your GitHub repository, go to:
Settings → Secrets and variables → Actions → New repository secret
Add each of the following secrets (case-sensitive names — copy them exactly):
| Secret name | What to paste into the Value field |
|---|---|
LEVO_AUTH_KEY | Your CLI authorization key. U.S. customers: copy it from app.levo.ai/settings/keys. Indian customers: copy it from app.india-1.levo.ai/settings/keys. |
LEVO_ORG_ID | Your Levo organization ID. U.S. customers: copy it from app.levo.ai/settings/organization. Indian customers: copy it from app.india-1.levo.ai/settings/organization. |
Open Settings → Secrets and variables → Actions again and confirm you see both LEVO_AUTH_KEY and LEVO_ORG_ID listed. If only one is there, the scan will fail with an authentication error.
Step 2 — Create the workflow file
In your repository, create a new file at this exact path:
.github/workflows/levo-code-scan.yml
Then paste in the YAML that matches your region.
For U.S. customers — paste this as-is:
name: Levo Code Scan
on:
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Levo Code Scanner
uses: levoai/actions/scan@v1-beta
with:
authorization-key: ${{ secrets.LEVO_AUTH_KEY }}
organization-id: ${{ secrets.LEVO_ORG_ID }}
app-name: "your-app-name"
language: "your-language"
For Indian customers — paste this version. The only difference is the extra saas-url: line, which points the Action at the Indian tenant. Without that line your scan will be uploaded to the U.S. tenant by mistake.
name: Levo Code Scan
on:
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Levo Code Scanner
uses: levoai/actions/scan@main
with:
authorization-key: ${{ secrets.LEVO_AUTH_KEY }}
organization-id: ${{ secrets.LEVO_ORG_ID }}
app-name: "your-app-name"
language: "your-language"
saas-url: "https://api.india-1.levo.ai" # required for Indian-tenant customers
Before you save the file, change just two values:
- Replace
your-app-namewith the name you want to see on the Levo dashboard (for example,payment-service). - Replace
your-languagewith your repository's language in lowercase, using one of the values listed earlier in Supported languages and frameworks.
Leave everything else exactly as shown — ${{ secrets.LEVO_AUTH_KEY }} and ${{ secrets.LEVO_ORG_ID }} automatically pull in the secrets you added in Step 1.
Step 3 — Commit and push
Commit .github/workflows/levo-code-scan.yml and push it to your main branch. GitHub will run the workflow on that push and on every push after it. Open the Actions tab in your repository to watch it run, then check your Levo dashboard — the application appears automatically on the first successful scan.
Action inputs
Required inputs (you must set all four)
| Input | Description |
|---|---|
authorization-key | Your CLI authorization key. Reference your secret as ${{ secrets.LEVO_AUTH_KEY }} (recommended) rather than pasting the key directly into the YAML. |
organization-id | Your Levo organization ID. Reference your secret as ${{ secrets.LEVO_ORG_ID }}. |
app-name | The name to display on the Levo dashboard. Created automatically on the first scan. |
language | Source language. One of: java, python, javascript, typescript, c, cpp, php, ruby, csharp. |
Optional inputs
If you are a U.S.-tenant customer, you can usually ignore this table on your first run — the defaults work for most U.S.-tenant repositories. If you are an Indian-tenant customer, review this table and set saas-url on your first run. Set the other inputs only if you need to change the matching behavior.
| Input | Default | When to set it |
|---|---|---|
saas-url | https://api.levo.ai | Indian-tenant customers must set this to https://api.india-1.levo.ai. U.S.-tenant customers can leave it unset. |
env-name | staging | Set to label the scan with a different environment (for example, production, qa). |
dir | . (repo root) | Set to a subdirectory path (for example, services/payments) to scan only part of a monorepo. |
scanner-docker-image | levoai/code-scanner:latest | Pin to a specific scanner image tag for reproducibility. Usually not needed. |
Action environment variable
The Action sets the following environment variable for use by downstream steps in the same job:
| Variable | Description |
|---|---|
scan-success | true if the scan succeeded, false otherwise. |
Setup — Azure Pipelines
The setup is three short steps:
- Create one
azure-pipelines.ymlfile at the repository root. - Create the pipeline in Azure DevOps.
- Add two secret pipeline variables (your Levo auth key and your Levo org ID), then run.
Step 1 — Create the pipeline file
In your repository, create a new file at the root:
azure-pipelines.yml
Then paste in the YAML that matches your region.
For U.S. customers — paste this as-is:
trigger:
branches:
include:
- main
pool:
vmImage: ubuntu-latest
variables:
LEVO_BASE_URL: 'https://api.levo.ai'
APP_NAME: 'your-app-name'
ENV_NAME: 'staging'
LANGUAGE: 'your-language'
steps:
- checkout: self
- bash: |
ARGS=(--app-name "$APP_NAME" --env-name "$ENV_NAME" --language "$LANGUAGE" --key "$LEVO_AUTH_KEY")
[ -n "$LEVO_ORG_ID" ] && ARGS+=(--organization "$LEVO_ORG_ID")
docker run --rm \
--user 0:0 \
-v "$BUILD_SOURCESDIRECTORY":/workspace/scan-results \
-e LEVO_BASE_URL="$LEVO_BASE_URL" \
-e LEVO_WORK_DIRECTORY=/workspace/scan-results \
-w /workspace/scan-results \
levoai/code-scanner:latest \
"${ARGS[@]}"
displayName: 'Run Levo Code Scanner'
env:
LEVO_AUTH_KEY: $(LEVO_AUTH_KEY)
LEVO_ORG_ID: $(LEVO_ORG_ID)
For Indian customers — the only difference is the LEVO_BASE_URL value, which points the pipeline at the Indian tenant. Skipping this will send your scan results to the U.S. tenant by mistake.
trigger:
branches:
include:
- main
pool:
vmImage: ubuntu-latest
variables:
LEVO_BASE_URL: 'https://api.india-1.levo.ai' # required for Indian-tenant customers
APP_NAME: 'your-app-name'
ENV_NAME: 'staging'
LANGUAGE: 'your-language'
steps:
- checkout: self
- bash: |
ARGS=(--app-name "$APP_NAME" --env-name "$ENV_NAME" --language "$LANGUAGE" --key "$LEVO_AUTH_KEY")
[ -n "$LEVO_ORG_ID" ] && ARGS+=(--organization "$LEVO_ORG_ID")
docker run --rm \
--user 0:0 \
-v "$BUILD_SOURCESDIRECTORY":/workspace/scan-results \
-e LEVO_BASE_URL="$LEVO_BASE_URL" \
-e LEVO_WORK_DIRECTORY=/workspace/scan-results \
-w /workspace/scan-results \
levoai/code-scanner:latest \
"${ARGS[@]}"
displayName: 'Run Levo Code Scanner'
env:
LEVO_AUTH_KEY: $(LEVO_AUTH_KEY)
LEVO_ORG_ID: $(LEVO_ORG_ID)
Before you save the file, change just two values:
- Replace
your-app-namewith the name you want to see on the Levo dashboard (for example,payment-service). - Replace
your-languagewith your repository's language in lowercase, using one of the values listed earlier in Supported languages and frameworks.
Leave everything else exactly as shown — $(LEVO_AUTH_KEY) and $(LEVO_ORG_ID) automatically pull in the secret variables you'll add in Step 3.
Step 2 — Create the pipeline in Azure DevOps
In Azure DevOps:
- Pipelines → New pipeline → Azure Repos Git → select your repository.
- Choose "Existing Azure Pipelines YAML file" → set Branch to
mainand Path to/azure-pipelines.yml→ Continue. - Save (not "Save and run" yet — you'll add the secret variables first).
Step 3 — Add the secret pipeline variables and run
On the pipeline page, click Edit → Variables → New variable. Add each of the following, with "Keep this value secret" ticked:
| Variable name | What to paste into the Value field |
|---|---|
LEVO_AUTH_KEY | Your CLI authorization key. U.S. customers: copy it from app.levo.ai/settings/keys. Indian customers: copy it from app.india-1.levo.ai/settings/keys. |
LEVO_ORG_ID | Your Levo organization ID. U.S. customers: copy it from app.levo.ai/settings/organization. Indian customers: copy it from app.india-1.levo.ai/settings/organization. |
Save, then Run. The pipeline triggers on every subsequent push to the configured branches. Check your Levo dashboard — the application appears automatically on the first successful scan.
Pipeline variables — Azure Pipelines
Secret variables (required)
These must be added as secret pipeline variables, not in the YAML.
| Variable | Description |
|---|---|
LEVO_AUTH_KEY | Your CLI authorization key. |
LEVO_ORG_ID | Your Levo organization ID. |
YAML-level variables (defined under variables: in azure-pipelines.yml)
| Variable | Default | When to change it |
|---|---|---|
LEVO_BASE_URL | https://api.levo.ai | Indian-tenant customers must set this to https://api.india-1.levo.ai. |
APP_NAME | — | The name to display on the Levo dashboard. |
ENV_NAME | staging | Environment label (e.g., production, qa). |
LANGUAGE | — | Source language: java, python, javascript, typescript, c, cpp, php, ruby, csharp. |
Approach 3: Bulk Scanning Script
Levo's Bulk Scanning Script generates OpenAPI specifications for every repository in a GitHub organization in a single run and imports them into your Levo dashboard. It is suited to security and platform teams that need a complete API catalog across an entire engineering organization, without instrumenting each repository individually.
Prerequisites
In addition to the page-level prerequisites, this approach requires:
- A GitHub Personal Access Token (PAT) with
reporead scope on the target organization's repositories. git,curl,python3, and a Bash shell (Git Bash on Windows; native on macOS and Linux).python3is used to pre-filter repositories by language via the GitHub API.- Docker Desktop configured with at least 4 CPUs and 8 GB of memory (Settings → Resources → Advanced).
The script uses timeout, which is not available on macOS by default. Install GNU coreutils via Homebrew:
- Install Xcode Command Line Tools:
xcode-select --install - Install Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - Install GNU coreutils:
brew install coreutils
macOS uses gtimeout (from coreutils) instead of timeout. The script handles this automatically once coreutils is installed.
How it works
- The script enumerates every repository in the configured GitHub organization using the GitHub API.
- Repositories written in unsupported languages are filtered out automatically and excluded from the scan, without being downloaded.
- Each remaining repository is cloned locally (latest commit only) and analyzed by the Levo scanner container.
- Multiple repositories are processed in parallel — a configurable number of containers run concurrently, each with bounded CPU and memory limits.
- After each scan completes, the cloned source is removed from the host and the generated OpenAPI specification is uploaded to your Levo dashboard over TLS.
Setup
Step 1 — Download the script
Click to download: bulk-scan.sh
Or download from your terminal:
curl -O https://docs.levo.ai/artifacts/code-scanner/bulk-scan.sh
Step 2 — Create a working folder
On your machine, create a new folder to hold the script and its output. We recommend naming the folder levo so all Levo-related files stay together. The folder can live anywhere — your home directory, your Desktop, or C:\ — as long as it has at least a few GB of free disk space (repositories are cloned here temporarily and removed after each scan).
Place the downloaded bulk-scan.sh inside this folder. The folder should look like:
levo/
└── bulk-scan.sh
The script will automatically create repos/ and results/ subfolders inside levo/ when it runs.
Step 3 — Fill in your credentials
Open bulk-scan.sh in any text editor (VS Code, Notepad, TextEdit, etc.). Near the top of the file you'll see a configuration block. Fill in the required values below, and set LEVO_ORG_ID only if you belong to multiple Levo organizations:
GITHUB_PAT="" # Your GitHub Personal Access Token (classic: `repo`, and possibly `read:org`; fine-grained: read access to repository contents/metadata)
GITHUB_ORG="" # Your GitHub organization name (e.g., my-company)
LEVO_AUTH_KEY="" # Get yours from https://app.levo.ai/settings/keys
LEVO_ORG_ID="" # Optional — leave empty if you only belong to one Levo organization
Save and close the file.
Step 4 — Run the script from inside the levo folder
Linux / macOS:
chmod +x bulk-scan.sh
./bulk-scan.sh
Windows (Git Bash):
bash bulk-scan.sh
Windows (PowerShell or Command Prompt, with Git for Windows installed):
bash bulk-scan.sh
Git for Windows ships a Bash interpreter and adds it to PATH, so the script runs from any Windows shell — Git Bash, PowerShell, or Command Prompt — without modification.
Configuration variables
| Variable | Required | Description |
|---|---|---|
GITHUB_PAT | Yes | GitHub Personal Access Token with repo read scope. |
GITHUB_ORG | Yes | The GitHub organization name. |
LEVO_AUTH_KEY | Yes | Levo CLI authorization key. Get it from app.levo.ai/settings/keys. |
LEVO_ORG_ID | No | Levo organization ID. Required only if you belong to multiple Levo organizations. |
LEVO_BASE_URL | No | Levo SaaS API URL. Default https://api.levo.ai. India: https://api.india-1.levo.ai. |
ENV_NAME | No | Environment label shown on the dashboard. Default staging. |
SCANNER_IMAGE | No | Scanner container image. Default levoai/code-scanner:3e0aa82 (pinned for reproducibility). |
MAX_PARALLEL | No | Number of repositories scanned concurrently. Default 4. |
CONTAINER_MEM | No | Memory cap per container. Default 2g. |
CONTAINER_CPUS | No | CPU cap per container. Default 1. |
Scaling parallelism
The script runs MAX_PARALLEL containers concurrently. Each container is hard-capped at CONTAINER_CPUS CPUs and CONTAINER_MEM memory. The defaults are tuned for Docker Desktop with 4 CPUs / 8 GB RAM.
When tuning, honor these two rules:
MAX_PARALLEL × CONTAINER_CPUS ≤ Docker Desktop CPUsMAX_PARALLEL × CONTAINER_MEM ≤ Docker Desktop memory
| Docker Desktop allocation | Recommended values | Behavior |
|---|---|---|
| 4 CPUs / 8 GB (default) | MAX_PARALLEL=4, CONTAINER_CPUS=1, CONTAINER_MEM=2g | Baseline |
| 8 CPUs / 16 GB | MAX_PARALLEL=8, CONTAINER_CPUS=1, CONTAINER_MEM=2g | ~2× throughput |
| 16 CPUs / 32 GB | MAX_PARALLEL=8, CONTAINER_CPUS=2, CONTAINER_MEM=4g | Faster per-scan, helpful for large repositories |
To bump Docker Desktop's allocation: Docker Desktop → Settings → Resources → Advanced → CPUs / Memory.
Estimated scan time
Per repository (single scan, under the default --cpus=1):
| Repository profile | Typical scan time |
|---|---|
| Small (simple Python / JS / Ruby service, fewer than ~20 endpoints) | 1–2 min |
| Medium (Java Spring Boot, TypeScript / Express, ~20–80 endpoints) | 3–6 min |
| Large (monorepo, many modules, deep dependency graph) | 10–20 min |
Repositories whose scan exceeds 30 minutes are automatically skipped to keep the bulk run moving and are reported in the final summary as timed out. To capture endpoints from these large repositories, scan them individually using Approach 1: Docker, which has no time limit and can handle very large monorepos.
Approximate wall-clock time for a 150-repository organization (assuming ~70% scannable after the language pre-filter, and an average ~4 minutes per scan):
MAX_PARALLEL | Approximate total time |
|---|---|
| 1 (serial) | ~7 hours |
| 4 (default) | ~1.5–2.5 hours |
| 8 | ~50–80 minutes |
Actual time depends on the mix of repository sizes, network speed, and disk I/O.
Output
On the dashboard: every successfully scanned repository becomes an application at https://api.levo.ai, with the generated OpenAPI specification imported and ready for inventory, risk scoring, and security testing.
On the terminal: a per-repository card is printed as each scan finishes, followed by a final summary reporting the number of repositories scanned successfully, failed, skipped (unsupported language or not accessible), and timed out, along with the total duration.
Limitations
The bulk scanning script supports source-code mode only. To import existing OpenAPI/Swagger specifications, use Approach 1 — Mode B.
Approach 4: GitHub App
Scan the repositories in your GitHub organization from one place. You create a single control repository from a Levo template; it scans the repositories you choose on a weekly schedule and uploads the discovered endpoints to your Levo dashboard. No workflow is added to your product repositories.
Scanning uses one GitHub App, which you create in your organization. It reads your code during a scan. Its private key is stored as a secret in your control repository, and Levo never receives it. Levo has no access to your repositories.
Prerequisites
In addition to the page-level prerequisites, this approach requires:
- Owner rights on the GitHub organization. Creating a GitHub App is restricted to organization owners; a member sees a 404 page instead.
- One or more repositories in a supported language.
You do not need to install Docker, the Levo CLI, or anything else on your local machine — scans run on your GitHub runners.
How it works
- A workflow in your control repository starts on a weekly schedule, or when you run it manually.
- It lists the repositories your scanning app has been granted and detects each repository's language.
- For each repository, it exchanges your scanning app's key for an access token that is valid for one hour, is limited to that one repository, and is revoked when the job ends.
- It clones the repository onto the runner and runs the
levoai/code-scannercontainer image against it. Four repositories are scanned in parallel. - The generated OpenAPI specification is uploaded over TLS to your Levo tenant, and the runner is discarded.
Source code never leaves your GitHub runners. Only the generated API specifications, with the application and environment names you configure, are uploaded to https://api.levo.ai.
Setup
The setup is five short steps:
- Create your control repository from the Levo template.
- Create your scanning app and install it on the repositories to scan.
- Add your secrets and variables to the control repository.
- Optionally, set your environment and application names.
- Run the first scan.
Step 1 — Create your control repository
Open github.com/levoai/levo-scan and click Use this template → Create a new repository:
| Field | Value |
|---|---|
| Owner | Your GitHub organization |
| Repository name | _levoai |
| Visibility | Private |
Anyone with write access to this repository can read the secrets you add in Step 3.
Step 2 — Create your scanning app
This app is the credential that reads your code. You create it and keep its private key; Levo never sees it.
In GitHub, go to:
Your organization → Settings → Developer settings → GitHub Apps → New GitHub App
Fill in the form:
| Field | Value |
|---|---|
| GitHub App name | Levo Scan - Your Company (for example, Levo Scan - Acme Corp). App names are unique across GitHub, so include your company name. |
| Homepage URL | https://levo.ai |
| Webhook → Active | Untick this box. This app never sends events to Levo. |
| Repository permissions → Contents | Read-only |
| All other permissions | No access |
Click Create GitHub App, then on the page that opens:
- Copy the App ID shown near the top. You need it in Step 3.
- Scroll down and click Generate a private key. A
.pemfile downloads. - Click Install App in the left menu, then Install next to your organization.
- Choose Only select repositories, select the repositories you want to scan, and click Install.
Select only repositories that serve an HTTP API. Frontend, test and infrastructure repositories have no endpoints to discover.
Step 3 — Add secrets and variables to your control repository
In your _levoai repository, go to:
Settings → Secrets and variables → Actions
On the Secrets tab, click New repository secret and add each of the following (case-sensitive names — copy them exactly):
| Secret name | What to paste into the Value field |
|---|---|
APP_PRIVATE_KEY | The full contents of the .pem file from Step 2, including the -----BEGIN and -----END lines. |
LEVOAI_AUTH_KEY | Your CLI authorization key, from app.levo.ai/settings/keys. |
LEVOAI_ORG_ID | Your Levo organization ID, from app.levo.ai/settings/organization. |
On the Variables tab, click New repository variable and add:
| Variable name | Value |
|---|---|
APP_ID | The App ID from Step 2. |
Copy your authorization key from app.india-1.levo.ai/settings/keys and your organization ID from app.india-1.levo.ai/settings/organization instead, and add one more variable: LEVOAI_BASE_URL with the value https://api.india-1.levo.ai. Without it, scan results are uploaded to the U.S. tenant.
Opening the .pem file in an editor can alter it. Copy it from the terminal instead — on macOS, cat ~/Downloads/YOUR-KEY.private-key.pem | pbcopy.
A repository secret can be read by a workflow on any branch, including an unmerged pull request. The scan workflow runs in an environment named levo-scan, which GitHub creates on the first run. Go to Settings → Environments → levo-scan, add APP_PRIVATE_KEY there as an environment secret, delete the repository secret, and set Deployment branches to your default branch only. Don't add a required reviewer: it would hold every weekly scan until someone approves it.
Step 4 — Set your environment and application names (optional)
Open levo-config.yml in your _levoai repository. Everything in it is optional:
version: 1
defaults:
env_name: staging
scan_timeout: 1800
repositories:
- repo: acme/payments-service
app_name: Payments API
- repo: acme/orders-service
language: kotlin
exclude:
- acme/internal-tooling
| Setting | Default | When to set it |
|---|---|---|
env_name | staging | Set to label every scan from this control repository with a different environment (for example, production, qa). Applies to all repositories. |
scan_timeout | 1800 | Set to give large repositories more time, in seconds (maximum 2400, 40 minutes). A repository that runs longer is stopped and reported; the others carry on. |
app_name | The repository name | Set per repository to display it under a different name on the Levo dashboard. Give each repository its own name — two repositories with the same name upload into the same application. |
language | Detected from the repository | Set per repository when the detected language is wrong. |
exclude | None | List repositories to skip without removing their access in Step 2. |
Step 5 — Run the first scan
In your _levoai repository, open the Actions tab, select Levo scan, and click Run workflow. After the first run, scans repeat weekly. To add a repository later, select it under your scanning app's Install App → Configure — no other change is needed.
Output
On the dashboard: every scanned repository becomes an application at https://api.levo.ai, named after the repository (or its app_name), with the generated OpenAPI specification imported and tagged with your environment.
In GitHub: each run has a plan job and one scan job per repository. The plan job log lists every repository it skipped and why (for example, archived, excluded, or an unsupported language). Each scan job log shows the result for that repository and the number of endpoints found.
Limitations
The GitHub App supports source-code mode only, for repositories hosted on GitHub. Archived repositories are skipped. One run scans at most 256 repositories; if more qualify, the run stops before scanning and says so. Narrow your scanning app's repository access, or add repositories to exclude in levo-config.yml. To import existing OpenAPI/Swagger specifications, use Approach 1 — Mode B.