Skip to main content

Run Surface Code Discovery in GitHub Actions

This guide adds Surface Code Discovery to one GitHub repository. Each push to main registers or updates the repository's APIs in APIsec. It also analyzes the code's dependencies, authentication, and committed secrets. With the settings in this workflow, your source code stays on the GitHub runner; only facts and observations about it reach APIsec.

Prerequisites​

  • A GitHub repository that contains your API's source code, in a supported framework. The workflow runs on pushes to main; change the branch name if yours differs.
  • An APIsec Personal Access Token with the Create App and Get App scopes, created by a user whose platform role is ADMIN or USER. Create it on the API Credentials page.
  • Your APIsec registration code, in the format ###-###, given to you by your APIsec Support resource. Each customer has their own code. If you don't have it, contact support.
  • Permission to add Actions secrets, variables, and workflows to the repository.

Step 1: Add the secrets​

In the repository, open Settings › Secrets and variables › Actions and, on the Secrets tab, add:

NameValue
APISEC_TOKENYour APIsec Personal Access Token
APISEC_REGISTRATION_CODEYour registration code. Each customer has their own; the CLI needs it on every run.

That's all the workflow needs. To update an application that already exists in APIsec, such as one the Code Bolt action created, you also set the APISEC_APPLICATION_ID variable. See Step 3.

Step 2: Add the workflow​

Create .github/workflows/apisec-surface.yml:

.github/workflows/apisec-surface.yml
name: APIsec Surface

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
discover:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: "3.11"

- name: Install the Surface CLI
run: |
pip install "apisec-surface==1.2.0"
apisec-surface telemetry off

- name: Pin the APIsec application
if: vars.APISEC_APPLICATION_ID != ''
env:
APISEC_APPLICATION_ID: ${{ vars.APISEC_APPLICATION_ID }}
run: |
mkdir -p apisec-code-bolt
printf 'applicationId: %s\n' "$APISEC_APPLICATION_ID" > apisec-code-bolt/state.yaml

- name: Discover and analyze APIs
env:
APISEC_PAT: ${{ secrets.APISEC_TOKEN }}
APISEC_REGISTRATION_CODE: ${{ secrets.APISEC_REGISTRATION_CODE }}
APISEC_SURFACE_LLM_EXTRACTION: "0"
run: apisec-surface analyze . --no-detect-monorepo --api-url https://api.apisecapps.com

Commit the file. The workflow runs on every push to main, and you can also start it from the Actions tab. It doesn't run on pull requests: a pull request's unmerged code would replace the application's inventory. Always pass --api-url: the CLI reads the APIsec URL only from this option, and the run fails without it. Use https://api.apisecapps.com unless APIsec gave you a different API URL.

What each environment variable does:

VariablePurpose
APISEC_PATAuthenticates the upload to APIsec.
APISEC_REGISTRATION_CODEIdentifies your installation. Every fresh runner is a new installation, so the CLI needs the code on every run, even with --dry-run. If the secret is missing or empty, the log shows "Please enter code (###-###):" followed by "Aborted!", and the step fails with exit code 1.
APISEC_SURFACE_LLM_EXTRACTION"0" keeps all source code on the runner. The CLI sends APIsec only facts and observations about the code, such as routes, called functions, dependency versions, and masked secrets. Endpoints come from static analysis of the code.

The CLI identifies the repository from the GITHUB_REPOSITORY variable that GitHub sets on every run, so each run updates the same application in APIsec. The Pin the APIsec application step is skipped unless you set APISEC_APPLICATION_ID in step 3, which points the workflow at an application that already exists. --no-detect-monorepo keeps the repository as one application. See Monorepos for the alternative.

apisec-surface telemetry off stops the CLI from sending anonymous usage data. The CLI does not commit files or open pull requests, so the workflow needs read access to the repository only. The workflow pins version 1.2.0; change the pin to upgrade. Versions before 1.2.0 can't identify the repository and register a new application on every run unless you set APISEC_APPLICATION_ID.

Step 3: Attach to an existing application (optional)​

Skip this step for a new repository. The first run registers an application in APIsec, and every later run updates it.

Set APISEC_APPLICATION_ID when the workflow must update an application that already exists: one the Code Bolt action created, or one registered by an earlier run with a CLI older than 1.2.0. The pinned application takes precedence over the repository identity.

  1. Find the application's ID. In the APIsec console, open the application; the ID is in the page URL after /application/. The View Results in APIsec link at the end of a workflow log contains it too.
  2. In Settings › Secrets and variables › Actions, on the Variables tab, add APISEC_APPLICATION_ID with that ID. It's not secret, so a variable is fine.
  3. Run the workflow. In Applications, that application updates and no new one appears.

Monorepos​

For a repository with several services, choose one of these:

  • Let the CLI split the repository. Remove --no-detect-monorepo from the analyze command. The CLI detects workspace boundaries, such as pnpm, npm, Maven, Gradle, .NET, Go, uv, and Poetry workspaces, and registers one application per sub-project, named after the repository and the sub-project's folder. Each run updates the same applications. The split ignores APISEC_APPLICATION_ID.
  • Run one job per service. Give each job the service's folder as the analyze path, keep --no-detect-monorepo, and pin a different application in each job, for example with an APISEC_APPLICATION_ID_ORDERS variable and the pin step pointed at that folder (mkdir -p services/orders/apisec-code-bolt). The pin is required here: every job identifies the repository the same way, so without pins all the jobs update one application.

If your services import shared workspace packages, such as route factories or authentication middleware in packages/shared, a job that analyzes only one service's folder can't resolve those imports, and it can miss routes or their authentication. For these repositories, analyze from the repository root as one application, or compare a per-service run with a root run before you rely on it.

Step 4: Adjust the scan (optional)​

Add any of these to the apisec-surface analyze command:

OptionEffect
A path instead of .Scans only that folder, for example apisec-surface analyze ./backend. If you pin an application, write the pin to the same folder (./backend/apisec-code-bolt/state.yaml).
--exclude "PATTERN"Skips files that match a glob pattern. Repeat the option for more patterns.
--timeout SECONDSSets how long the run waits for APIsec to finish verifying the analysis. The default is 30 minutes. A lower value saves runner minutes; the analysis continues in APIsec either way.
--dry-runAnalyzes the code and prints a summary, but uploads nothing. APISEC_REGISTRATION_CODE is still required.
--fail-on secretsFails the run with exit code 3, before anything is uploaded, when the scan finds a credential, such as an API key, committed to the repository.

Verify​

  1. Open the workflow run in the Actions tab. The Discover and analyze APIs step finishes green and the log lists the routes and frameworks it found, followed by a link to the results.
  2. In the APIsec console, open Applications. The repository (or, with one job per service, each service) appears as an application with its endpoints, frameworks, and libraries.
  3. In the application, dependency vulnerabilities that the analysis traced to an endpoint appear as predicted exploits. An APIsec scan of the running API later confirms or clears each one.

To test the setup without uploading, add --dry-run to the analyze command in the workflow and run it once.

note

For large applications, verification can take longer than the run waits. The run then logs "Stopped waiting for verification" and still finishes green. The analysis continues in APIsec, and the results appear at the link in the log.

Troubleshoot​

Use the exit code and the error in the log to find the cause.

Exit code or symptomCauseFix
1 with "Please enter code" and "Aborted!", or 4 with "No registration code found"The APISEC_REGISTRATION_CODE secret is missing or emptyAdd the repository secret and pass it in env, as in step 2.
4, with "Cannot register application in CI"The CLI couldn't identify the repository, because GITHUB_REPOSITORY didn't reach itRun the CLI directly on the runner, as in step 2. If the step runs in a container, pass GITHUB_REPOSITORY into it.
Each run creates a new applicationThe workflow pins a CLI older than 1.2.0 and APISEC_APPLICATION_ID is not setChange the pin to 1.2.0, or set APISEC_APPLICATION_ID as in step 3. With one job per service, every job needs its own pin.
1, analysis errorThe CLI couldn't analyze the codeCheck the path you passed, and that the code uses a supported framework.
2, with HTTP 401 in the logThe token is invalid or expiredCreate a new token and update the APISEC_TOKEN secret.
2, with HTTP 403 in the logThe token lacks a scope, or its creator lacks a roleUse a token with Create App and Get App, created by an ADMIN or USER.
2, with SSL certificate verify failed--api-url is wrongUse https://api.apisecapps.com, or the API URL APIsec gave you.
3, policy violation--fail-on secrets found a committed credentialRemove the credential from the repository.
4, configuration errorA required setting is missing or invalidRead the error in the log. It names the missing setting.
6, memory limitThe repository is too large for the runnerScan a subfolder, or exclude generated and vendored code with --exclude.
Green run, no endpointsNo supported framework was foundPoint the analyze command at the folder that contains the API code.

Migrate from the Code Bolt GitHub Action​

Code Bolt is now Surface Code Discovery. If a workflow uses apisec-inc/apisec-bolt-code-discovery-github-action, replace it with the workflow on this page. The old inputs map as follows: pass the folder as the analyze path instead of repo-path, and add --dry-run instead of dry-run: true. The CLI has no host-url option; set the API's base URL on the application in APIsec instead.

  1. Replace the uses: apisec-inc/apisec-bolt-code-discovery-github-action step with the actions/setup-python, Install the Surface CLI, and Discover and analyze APIs steps from step 2.
  2. Pass your token as APISEC_PAT. You can keep the existing secret, for example APISEC_PAT: ${{ secrets.API_DISCOVERY_TOKEN }}, if the token has the Create App and Get App scopes.
  3. Add the APISEC_REGISTRATION_CODE secret. To keep the old action's application, add the Pin the APIsec application step and the APISEC_APPLICATION_ID variable.
  4. Remove contents: write and pull-requests: write from the workflow's permissions.
  5. Delete the apisec-bolt-code-discovery/ folder from the repository, and close any open pull requests that the old action created. The Surface CLI doesn't use that folder.

To keep the application the old action created, set APISEC_APPLICATION_ID to its ID before the first run. Otherwise the first run registers a new application. Copy any settings you need, such as the base URL and authentication, to it, then delete the old one. The published specification lives on the application in APIsec, not in the repository.