Blog
AppSec Blog

How to automate API security testing

 - 
July 7, 2026

Most teams that say they’ve automated API security testing have actually only automated API scanning by setting up a tool that crawls the endpoints, throws known payloads at them, and reports what matches a signature. That’s a reasonable start, but it won’t catch the vulnerabilities responsible for most real API breaches: broken object-level authorization, broken function-level authorization, and authentication weaknesses that only show up when you test with multiple authenticated user roles at once.

This guide walks through the difference and shows how to build a CI/CD pipeline that runs the kind of authenticated, multi-role testing a security engineer would do by hand – automatically, on every deploy.

You information will be kept Private
Table of Contents

There’s a difference between running an automated scanner against your API and actually running automated security testing of your API – and the gap between the two is where most breaches happen.

An automated scanner crawls your endpoints, injects known attack payloads, and reports responses that match vulnerability patterns. That’s useful work, but it’s not the same as testing whether your API correctly enforces per-object authorization across two authenticated sessions, whether a standard user can invoke an admin-only function, or whether your JSON Web Token (JWT) signing secret can be cracked offline. Those aren’t edge cases – they are where the top API security risks of broken object-level authorization (BOLA), broken function-level authorization (BFLA), and broken authentication live. Taken together, they account for most of the API breaches that make headlines.

Automated security testing does what a security engineer would do manually: authenticate with multiple accounts, substitute object identifiers between sessions, invoke admin-only functions from a standard user session, probe rate-limiting thresholds, confirm injection is actually exploitable. The difference is that it does this at the speed and consistency a pipeline requires.

API attacks are outpacing website attacks by a wide margin – average attacks per API host ran 72% higher than attacks per website in the first half of 2025, according to Indusface’s State of Application Security report. Separately, Total Shift Left’s research puts API-related incidents at 43% of all data breaches in 2025, up 12% year over year. Meanwhile, organizations that build security testing into the pipeline rather than bolting it on at the end report up to a 60% reduction in production vulnerabilities and as much as a 90% reduction in remediation costs.

This guide walks through building automated API security testing that actually covers what matters: the OWASP API Security Top 10, authenticated authorization testing, and injection testing with exploit confirmation – not just a scan report.

What automated API security testing actually is

Automated API security testing is the practice of programmatically executing structured security tests against API endpoints – including authenticated multi-role authorization testing, injection testing with exploit confirmation, authentication weakness detection, and stateful multi-step workflow testing – without requiring a security engineer to run each test by hand. It differs from automated scanning because it tests specific vulnerability classes through contextual, authenticated interaction rather than matching traffic against known signatures.

What the automation needs to cover

Before touching pipeline configuration, it’s worth being explicit about vulnerability scope. The seven classes that matter most for automated coverage map directly onto the OWASP API Security Top 10:

  • API1:2023 Broken Object-Level Authorization: Addressed by multi-user authenticated object-level authorization testing
  • API2:2023 Broken Authentication: Addressed by JWT weakness testing, OAuth flow validation, rate-limiting checks, missing authentication
  • API3:2023 Broken Object Property-Level Authorization: Addressed by mass assignment testing, response over-exposure analysis
  • API4:2023 Unrestricted Resource Consumption: Addressed by endpoint-level rate-limiting presence and effectiveness
  • API5:2023 Broken Function-Level Authorization: Addressed by multi-role function-level authorization testing
  • API8:2023 Security Misconfiguration: Addressed by header analysis, TLS configuration, default credential testing
  • API9:2023 Improper Inventory Management: Addressed by shadow and zombie endpoint detection via discovery

A standard scanner may already handle some of this but fundamentally cannot handle the rest because that requires context a signature-matching tool doesn’t have. Getting all this context and systematically acting on it is why you need to build a deliberate pipeline instead of pointing a generic scanner at a staging URL and calling it done:

Vulnerability class Standard scanner capabilities Capabilities needed for testing with authenticated context
Injection (SQLi, XSS, SSRF, XXE) Pattern-based detection Proof-based exploit confirmation
Missing authentication Unauthenticated probing Systematic per-endpoint verification
Security misconfiguration Header and TLS checks Deployment configuration verification
BOLA No multi-user context Cross-session object substitution testing
BFLA No multi-role context Cross-role function invocation testing
Broken authentication (JWT/OAuth) Missing-auth checks only Token lifecycle, algorithm confusion, weak secrets
Business logic flaws No workflow context Stateful multi-step workflow testing
Blind injection No out-of-band capability Out-of-band confirmation

Building the automation architecture

There are four layers to get right: 

  • What to test
  • How to authenticate 
  • What vulnerability classes to run
  • How to act on what comes back. 

Before working through them, it’s worth mentioning one technicality to clarify the whole process discussed below. Some API DAST tools simply expose a REST endpoint for triggering scans (POST /scans with a JSON body, then poll for a scan_id), but the workflow here is based specifically on how the Invicti Platform works. 

The Invicti Platform integration runs as a Docker container (invicti-scan-cli) configured through environment variables, not a script that calls a scanning API directly. Your pipeline pulls the container, sets a handful of INVICTI_* variables, and runs it; the container handles triggering the scan, waiting for it to finish, and reporting results back into Invicti Platform. There’s no separate polling loop to write. 

The four layers below map onto that model, and the code samples reflect it, though some of the values are illustrative rather than ready-to-paste – see Invicti’s official docs for those.

Layer 1: Define the API surface

The scanner needs to know what it’s testing before it can test it. For API targets, this comes down to how the target itself is configured in Invicti Platform, not something you re-specify on every pipeline run:

  • Option A – import an OpenAPI or Swagger specification. If you maintain a spec, this is the cleanest path: it gives the scanner every endpoint, method, parameter, and declared security scheme up front. You attach the spec when you set up the target in Invicti Platform, not inline in the pipeline script.
  • Option B – import a Postman collection. Useful if your team already documents endpoints there, and configured the same way, at the target level.
  • Option C – capture traffic through a proxy and import the resulting HAR file. This works for APIs without a formal specification, though it only captures what the proxy actually observed, so coverage is only as good as the traffic you generate.

The practical implication of the whole approach is that your CI/CD script doesn’t carry the API definition. It references a target ID that already has one attached.

# Illustrative only: retrieving a target ID to reuse across pipelines
curl -X GET \
  "https://platform.invicti.com/api/inventory/v1/assets?assetType=target&pageSize=50&pageNumber=1" \
  -H "accept: application/json" \
  -H "X-Auth: $INVICTI_API_TOKEN"
# Target ID is in the response at $.items[0].id

Target IDs are stable, so you can save them alongside the application name once and reuse them across every pipeline that scans that target.

Layer 2: Configure authentication

This is the layer that separates security testing from scanning. Without authenticated, multi-role access, BOLA, BFLA, and most authentication weaknesses are simply invisible to the tool. Here’s where the divergence from the typical “pass tokens into the scan request” pattern matters most: Invicti doesn’t take standard-user and admin-user credentials as pipeline parameters. Authentication is configured once on the target itself, in Invicti Platform, and the scan-cli container picks it up automatically at scan time using the target ID you pass in.

For multi-role, multi-session testing specifically, Invicti has a purpose-built method: IDOR / BOLA authentication, set once per API scan target. It’s designed around exactly the two-session comparison BOLA and BFLA testing depend on:

  • Admin: one credential set, higher-privilege role
  • Standard User A: one credential set, standard-privilege role
  • Standard User B: a second standard-privilege credential set

Exactly one of these is marked Default, and each uses an API key, basic authentication, or bearer token credentials.

Comparing the two Standard users tests horizontal access control, meaning whether one user can reach another same-level user’s data, which is the core BOLA scenario. Comparing a Standard user against the Admin user tests vertical access control, meaning whether a low-privilege user can reach admin-only functions, which is the BFLA scenario. Invicti signs in as each configured user and compares what they can each reach, so the comparison itself is what surfaces the vulnerability, not a separate rule written against specific endpoints.

One caution to keep in mind: multi-session tests like these actively attempt to bypass access controls, which makes them potentially riskier to run than a standard scan. Stick to staging or test environments, not live production setups (which is the best practice anyway).

The same credentials can also be managed from the API catalog rather than the target’s Authentication settings, which is convenient if you’re administering many APIs centrally. With the Invicti approach, the target-settings route is still the more robust and recommended default, since credentials entered there apply from the moment scanning starts.

The pipeline side of authentication is much simpler than the target side: it’s just the API token that lets the scan-cli container talk to Invicti Platform at all, stored as a CI/CD secret.

# Illustrative: the pipeline only needs to authenticate to Invicti Platform,
# not to the API under test - target auth is already configured separately
env:
  INVICTI_API_TOKEN: ${{ secrets.INVICTI_API_TOKEN }}

Layer 3: Set the scan profile

The scan profile (configured in Invicti Platform, then referenced by name from the pipeline) determines which vulnerability classes get tested and at what depth. This is also configured ahead of time rather than assembled inline in a script.

The profile itself – built once in the UI, or via the platform API if you’re managing it as code – is where you turn on the vulnerability classes that matter for API testing: BOLA, BFLA, injection with exploit confirmation, broken authentication, and the rest of the OWASP API Top 10 categories described earlier. The pipeline’s job is just to point at the right profile by name.

Layer 4: Validate findings and gate on them

The last piece is deciding what happens with the results. Here again, the mechanism is different from a script that fetches a findings array and filters it: build-failure conditions are typically set when you generate the CI/CD script from the level of Invicti Platform integrations, where you can set thresholds like “fail the build on confirmed critical or high findings.” The container then exits with a failing status code if that threshold is crossed, and your pipeline treats that exit code as the gate.

Conceptually, the logic your pipeline is enforcing looks like this, even though you’re not writing the filtering code by hand:

IF any finding has severity >= threshold 
  AND finding state == confirmed
THEN fail the build
ELSE pass

That confirmed-only condition is the single most important idea in this whole pipeline, however it’s implemented. It’s the difference between a gate developers trust and one they learn to route around – more on that below.

Wiring it into your pipeline

Across every CI/CD platform, the shape is the same: pull the invicti-scan-cli container, set a handful of environment variables, and run it as a build step, ideally after your application is built and deployed to a test environment rather than against source code. The container triggers the scan against the target, waits for it to finish, and (depending on how build-failure conditions are configured) exits with a non-zero status if the results cross your threshold. There’s no separate “wait for scan completion” step to write – the container blocks until the scan is done.

Invicti Platform also has a script generator that produces a working starting point with the right syntax and variables for a given platform and lets you set build-failure conditions visually. The examples below show the general shape, but the individual generated script is always the source of truth.

GitHub Actions integration script

# .github/workflows/api-security-testing.yml
name: Automated API security testing

on:
  push:
    branches: [staging]
  pull_request:
    branches: [main]

jobs:
  api-security-test:
    runs-on: ubuntu-latest
    container:
      image: invicti/scan-cli:latest
    env:
      INVICTI_API_BASE_URL: "https://platform.invicti.com"
      INVICTI_API_TOKEN: ${{ secrets.INVICTI_API_TOKEN }}
      INVICTI_TARGET_ID: ${{ secrets.INVICTI_API_TARGET_ID }}
      INVICTI_SCAN_PROFILE: "API Full Security Test"
      INVICTI_SCAN_AGENT: "EphemeralAgent"
    steps:
      - name: Run Invicti API security scan
        run: /home/invicti/scancli/ScanCLI

      - name: Upload scan artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: invicti-api-scan-results
          path: |
            ./scan-results/**
            ./reports/**
          if-no-files-found: ignore
          retention-days: 7

Note the INVICTI_SCAN_AGENT value. Invicti’s regular cloud agent works for publicly reachable targets, but a staging API sitting behind a firewall – which is the normal case for an authenticated, multi-role testing scenario – needs EphemeralAgent instead. That type of agent spins up temporarily inside the CI/CD environment itself, runs the scan from there, and tears down afterward, so it can reach a target the cloud agent never could.

Jenkins integration script

Jenkins needs the Docker Pipeline plugin, but the stage itself just runs the same container with the same environment variables:

// Jenkinsfile — API security testing stage
pipeline {
    agent any
    environment {
        INVICTI_API_BASE_URL = "https://platform.invicti.com"
        INVICTI_API_TOKEN = credentials('invicti-api-token')
        INVICTI_TARGET_ID = credentials('invicti-api-target-id')
        INVICTI_SCAN_PROFILE = "API Full Security Test"
        INVICTI_SCAN_AGENT = "EphemeralAgent"
    }
    stages {
        stage('API security testing') {
            when { branch 'staging' }
            steps {
                sh 'docker pull invicti/scan-cli:latest'
                sh '''
                  docker run --rm \
                    -e INVICTI_API_BASE_URL \
                    -e INVICTI_API_TOKEN \
                    -e INVICTI_TARGET_ID \
                    -e INVICTI_SCAN_PROFILE \
                    -e INVICTI_SCAN_AGENT \
                    invicti/scan-cli:latest
                '''
            }
        }
    }
}

The container’s own exit code carries the pass/fail signal, based on whatever build-failure conditions you set when configuring the scan profile or the generated script – Jenkins doesn’t need separate logic to poll a scan ID or evaluate a findings list.

GitLab CI integration script

# .gitlab-ci.yml — API security testing stage
api-security-test:
  stage: security
  image: invicti/scan-cli:latest
  only:
    - staging
    - merge_requests
  variables:
    INVICTI_API_BASE_URL: "https://platform.invicti.com"
    INVICTI_SCAN_PROFILE: "API Full Security Test"
    INVICTI_SCAN_AGENT: "EphemeralAgent"
  script:
    - /home/invicti/scancli/ScanCLI

INVICTI_API_TOKEN and INVICTI_TARGET_ID should come from GitLab’s masked CI/CD variables rather than being written into the file, the same way you’d handle any other credential.

All these examples are conceptual rather than copy-paste-exact. If you’re an Invicti user, confirm variable names, image tags, and available options against Invicti’s CI-driven scans documentation and the linked CI environment variables reference before building against them.

Configuring BOLA and BFLA automation specifically

This is the section that most “automate your API scanning” guides skip, because it’s the part that actually requires planning rather than just pointing a tool at a URL. 

Invicti’s approach, as described in Layer 2 above, uses a single authentication method – IDOR / BOLA authentication, configured per API target – to drive both BOLA and BFLA testing, since both are really the same underlying mechanism (comparing what different authenticated users can reach) applied at two different levels.

Testing for BOLA

BOLA is the horizontal case: two users at the same privilege level, one reaching data that belongs to the other. For a closer look at the vulnerability itself and detection methods, see How to detect BOLA in APIs

For BOLA testing to produce meaningful results in an automated pipeline, a few things need to be true:

  1. Two Standard-level credential sets are configured on the target’s IDOR / BOLA authentication settings, each representing a distinct user.
  2. Each of those users owns distinct resources in the environment under test – orders, documents, profiles – that belong exclusively to them.
  3. The scanner signs in as each user and compares what one can reach against what the other can reach, flagging cases where User A’s session returns User B’s data or vice versa.

The part teams most often miss isn’t the scanner configuration but the test data behind it. Without realistic, distinctly owned objects for each user, there’s nothing for the comparison to catch, and the scan will silently produce zero BOLA findings – not because the API is secure, but because there was nothing to check. Here’s an example of setting up BOLA seed data:

# Illustrative: seed data for BOLA testing, run once during environment setup,
# independent of scanner configuration
def seed_bola_test_data(standard_user_a_id, standard_user_b_id):
    for _ in range(5):
        db.execute(
            "INSERT INTO orders (user_id, total, status) VALUES (?, 99.99, 'completed')",
            (standard_user_a_id,)
        )
    for _ in range(5):
        db.execute(
            "INSERT INTO orders (user_id, total, status) VALUES (?, 299.99, 'completed')",
            (standard_user_b_id,)
        )
    db.commit()

Testing for BFLA

BFLA is the vertical case: a low-privilege user reaching functions meant only for a higher-privilege role. See How to test for BFLA for the underlying vulnerability and detection mechanics. 

BFLA testing uses the same IDOR / BOLA authentication setup, just comparing a different pair of credential sets – a Standard user against the Admin user, rather than two Standard users against each other. The scanner signs in as both, then checks whether the Standard-level session can successfully invoke functions that should require the Admin credential.

The setup requirement that matters most here is privilege separation: the Admin credential needs to actually have elevated access in the target application, and the Standard credential needs to actually lack it. If both accounts happen to share the same effective permissions – a common mistake in staging environments that were cloned from a single seed user – the comparison has nothing to detect, for the same reason a BOLA scan with identical users finds nothing.

Handling false positives

The confirmed-only gate condition described above is worth dwelling on, because it’s the decision that determines whether teams keep this pipeline running or quietly disable it after a few weeks – and what sets Invicti apart from many other tools.

Potential findings, i.e. pattern matches that might be real but haven’t been confirmed, get tracked for visibility but shouldn’t block a deployment. Confirmed findings, where proof-of-exploit evidence exists, should. This is proof-based scanning applied as an operational rule rather than a marketing term: only findings that have been shown to be exploitable get to fail a build.

The reason this matters is developer trust. A gate that blocks builds on theoretical pattern matches will produce false-positive failures often enough that developers stop taking it seriously, and a security gate nobody trusts gets bypassed or turned off. A gate that only blocks on confirmed, exploitable findings is trusted, respected, and acted on.

Rate limiting on the API under test can also produce misleading results – if the target throttles or rejects scan traffic, you get incomplete coverage rather than a clean report. For the Invicti Platform, scan throughput and timeout settings are configured on the scan profile or target, not passed as pipeline parameters. If the API under test enforces aggressive rate limits, that’s worth tuning before you can fully rely on the pipeline’s results.

Extending automation for other API patterns

GraphQL

GraphQL routes everything through a single endpoint, so discovery works differently than for a REST API: the scanner needs schema introspection rather than per-path crawling. Mutations also deserve particular attention, since that’s typically where BFLA-style issues show up. 

When configuring a GraphQL scan target, make sure introspection is enabled (or a schema is supplied directly) and that mutation testing isn’t disabled by default – a scan that only exercises queries will miss most of the authorization surface that matters.

Microservices

For a microservices architecture, each service is generally its own scan target, with its own target ID, its own authentication configuration, and potentially its own scan profile (if services differ meaningfully in what they expose). A pipeline testing multiple services typically runs the scan-cli container once per service, with each invocation pointed at a different INVICTI_TARGET_ID, rather than trying to fold multiple services into a single scan configuration.

Measuring whether it’s actually working

A pipeline that runs on every deploy isn’t automatically a good pipeline. Four metrics tell you whether the automation is doing real work:

  • Confirmed finding rate: What share of reported findings are confirmed exploitable, versus theoretical? With proof-based scanning in place, this should sit above 95%. A lower number usually points to scan profile misconfiguration – or a subpar tool.
  • Vulnerability class coverage: What percentage of the covered OWASP API Top 10 categories produced at least one test execution in the last scan? Gaps here usually trace back to missing authentication configuration or incomplete API surface definition, not missing tooling.
  • BOLA/BFLA execution rate: Of endpoints that accept object identifiers, what share were actually tested for BOLA with cross-user sessions? Of admin-gated functions, what share were tested for BFLA from a standard user session? Low numbers point to missing test accounts or seed data, not a weak scanner.
  • Regression detection rate: Of vulnerabilities fixed in a prior sprint, what percentage were re-confirmed if reintroduced? This should be 100% – anything less means the pipeline isn’t covering the surface it should be.

Automating API security testing that finds what really matters

Automated scanning finds patterns. Automated API security testing – multi-role authenticated BOLA testing, cross-role BFLA testing, authentication weakness detection, and injection testing with exploit confirmation – finds the vulnerabilities behind most real API breaches. The Invicti platform brings proof-based scanning to API testing so that pipeline gates only block on confirmed, exploitable issues instead of suspected pattern matches.

Next steps

Frequently asked questions

FAQs about automating API security testing

What is automated API security testing?

It’s the programmatic execution of structured security tests against API endpoints – multi-role authenticated BOLA testing, function-level authorization testing, injection testing with exploit confirmation, authentication weakness detection, and stateful workflow testing – without a security engineer running each test by hand. It differs from automated scanning in that it tests specific vulnerability classes through authenticated, contextual interaction rather than signature matching.

What’s the difference between automated API scanning and automated API security testing?

Scanning crawls endpoints, injects known payload patterns, and flags matches. It’s fast and covers a wide surface, but it can’t test authorization failures that require multi-user or multi-role context, can’t confirm whether an injection point is actually exploitable, and can’t test authentication weaknesses that require specific token manipulation. Security testing simulates the fuller attacker workflow – authenticated sessions, cross-user access attempts, role escalation – and produces confirmed findings instead of pattern-match alerts.

What vulnerabilities should automated API security testing cover?

At minimum, the OWASP API Security Top 10: BOLA, broken authentication, broken object property authorization, unrestricted resource consumption, BFLA, security misconfiguration, injection, and improper inventory management. BOLA and BFLA are the categories that differentiate real testing from scanning, since both require two authenticated sessions running at once.

How do you configure authentication for automated API security testing?

Rather than passing credentials into each pipeline run, authentication for the API under test is configured once against the target itself. Invicti, for example, has a dedicated IDOR / BOLA authentication method on API targets that takes an Admin credential set and two Standard credential sets, so it can compare sessions and detect access control issues. The pipeline itself then only needs to authenticate to the testing platform, usually with a single API token stored as a CI/CD secret.

What is proof-based testing, and why does it matter for automation?

Proof-based testing confirms a vulnerability is actually exploitable before it’s reported, rather than flagging a pattern match that might or might not be real. In an automated pipeline this is what makes a reliable gate possible: only confirmed findings block a build. Without it, gates fail on theoretical issues often enough to erode developer trust within weeks, and teams either disable the gate or downgrade it to informational-only.

How do you automate BOLA testing specifically?

You need two credential sets at the same privilege level, each belonging to a user who owns distinct resources in the environment under test, configured wherever your platform sets up multi-session authentication. The platform signs in as both users and compares what each can reach, flagging cases where one user’s session returns the other’s data. None of this works without realistic seed data behind those accounts – without actual owned objects, there’s nothing to cross-test.

How long does automated API security testing take in a CI/CD pipeline?

It depends on scope. A lightweight scan of recently changed endpoints, suited to PR-level feedback, typically finishes in a few minutes. A full authenticated scan covering BOLA, BFLA, injection, and authentication testing across 50 to 200 endpoints generally runs somewhere in the 15-to-45-minute range, which is still compatible with pre-release gating in most pipelines.

How is this different from manual penetration testing?

Automated testing gives consistent, repeatable coverage of known vulnerability classes on every deployment. Manual pentesting goes deeper on complex attack chains, business logic flaws that need human reasoning, and novel vulnerability classes automation doesn’t yet cover. Most mature programs run both: automated testing continuously, manual pentesting periodically for depth.

What CI/CD platforms does this kind of testing typically integrate with?

Most API DAST platforms, including Invicti, support GitHub Actions, Jenkins, GitLab CI, Azure Pipelines, and similar tools, generally through a container-based CLI configured with environment variables rather than a custom script per platform. Triggers can be configured for pull requests, staging deployments, or scheduled scans, and build-failure conditions determine whether confirmed findings above a set severity threshold cause the pipeline to fail. Findings are usually exportable to issue trackers like Jira or ServiceNow so remediation routes to the right team automatically.

Table of Contents