SYSTEMS OPERATIONAL — All security modules running
API reference

SecuProbe API documentation

Automate security scanning in your CI/CD pipeline with the SecuProbe REST API. A Free account includes 10 API scans; the SARIF export, pull request comments and deploy hooks start with the Starter plan. Get your API key →

Authentication

All API v1 requests require your API key in the Authorization header as a Bearer token. API keys start with sp_live_.

bash
curl https://secuprobe.io/api/v1/scans \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY"

Generate API keys in Dashboard → Settings → API. Keep them secret: do not commit them to version control, and store them as CI/CD secrets.

Rate limits

Rate limits are applied per API key per minute. Requests over the limit return 429 Too Many Requests.

PlanRequests / minuteAPI scans
Free1010 (trial)
Starter30Unlimited
Pro60Unlimited
Business120Unlimited
Enterprise200Unlimited

Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp).

Endpoints

Base URL: https://secuprobe.io/api/v1

POST/api/v1/scansscope: scans:create

Launch a security scan. The URL must target a validated domain of your account: an API key cannot scan anything else. The request returns immediately with a scan ID; poll GET /scans/:id for the status.

bash
curl -X POST https://secuprobe.io/api/v1/scans \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-domain.com" }'
json
{
  "scanId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "estimatedDuration": 120,
  "resultsUrl": "https://secuprobe.io/api/v1/scans/550e8400-e29b-41d4-a716-446655440000",
  "reportUrl": "https://secuprobe.io/scan/550e8400-e29b-41d4-a716-446655440000"
}
GET/api/v1/scans/{id}scope: scans:read

Get the status and summary of a scan. Poll until the status is 'completed' or 'failed'.

bash
curl https://secuprobe.io/api/v1/scans/SCAN_ID \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY"
json
{
  "scanId": "550e8400-...",
  "url": "https://your-domain.com",
  "status": "completed",
  "secuScore": 78,
  "vulnerabilitiesCount": {
    "critical": 1,
    "high": 3,
    "medium": 5,
    "low": 8,
    "info": 12
  },
  "completedAt": "2026-03-10T12:00:00Z",
  "reportUrl": "https://secuprobe.io/scan/550e8400-..."
}
GET/api/v1/scans/{id}/vulnerabilitiesscope: scans:read

List all vulnerabilities found in a scan, with descriptions and remediation guides.

bash
curl https://secuprobe.io/api/v1/scans/SCAN_ID/vulnerabilities \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY"
json
{
  "total": 29,
  "vulnerabilities": [
    {
      "id": "vuln-uuid",
      "title": "Missing Content-Security-Policy",
      "severity": "high",
      "category": "Security Headers",
      "description": "No CSP header found...",
      "remediationGuide": "1. Add a Content-Security-Policy header...",
      "endpointUrl": "https://your-domain.com/",
      "httpMethod": "HEAD",
      "parameter": "content-security-policy",
      "cvssScore": 5.4,
      "cvssVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "cweId": "CWE-693",
      "owaspCategory": "A05:2021",
      "fingerprint": "66f6ca2a69942a73bbe8649d296f018215362778",
      "reproSteps": ["Send a HEAD request to https://your-domain.com/."],
      "curlCommand": "curl -I -s 'https://your-domain.com/'",
      "evidence": { "observed": "Header "content-security-policy" is absent from the response." }
    }
  ]
}
GET/api/v1/scans/{id}/summaryscope: scans:read

Get the verdict for a severity threshold (fail-on), the findings that are new or fixed since the previous scan of the same host, and a Markdown comment ready to post. Findings marked as accepted risk or false positive do not fail the verdict.

bash
curl "https://secuprobe.io/api/v1/scans/SCAN_ID/summary?fail-on=high" \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY"
json
{
  "scanId": "550e8400-...",
  "status": "completed",
  "secuScore": 78,
  "counts": { "critical": 0, "high": 1, "medium": 5, "low": 8, "info": 12 },
  "gate": { "failOn": "high", "failed": true, "blocking": 1, "ignored": 0 },
  "comparedTo": "3f2b1c9e-...",
  "newFindings": [
    { "id": "vuln-uuid", "title": "Reflected XSS", "severity": "high", "url": "https://your-domain.com/search" }
  ],
  "fixedFindings": [],
  "markdown": "<!-- secuprobe:scan-comment -->\n### SecuProbe security scan: failed ...",
  "marker": "<!-- secuprobe:scan-comment -->",
  "reportUrl": "https://secuprobe.io/scan/550e8400-..."
}
GET/api/v1/scans/{id}/sarifscope: scans:read

Export a completed scan as SARIF 2.1.0, for GitHub code scanning and any compatible tool. The file contains no request or response evidence. Starter plan and above.

bash
curl https://secuprobe.io/api/v1/scans/SCAN_ID/sarif \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY" \
  -o secuprobe.sarif
GET/api/v1/domainsscope: domains:read

List all validated domains in your account.

bash
curl https://secuprobe.io/api/v1/domains \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY"
json
{
  "domains": [
    {
      "id": "domain-uuid",
      "hostname": "your-domain.com",
      "isValidated": true,
      "createdAt": "2026-01-15T10:00:00Z"
    }
  ]
}

Error codes

HTTP codeMeaningAction
400Bad requestCheck the request body and parameters
401UnauthorizedVerify that the API key is valid and active
403ForbiddenThe action requires a higher plan
404Not foundThe resource does not exist or is not yours
429Rate limitedSlow down and check the X-RateLimit-Reset header
500Server errorRetry with exponential back-off

Error responses follow the format: { "error": "Human readable message" }

GitHub Actions

Use the official SecuProbe GitHub Action to scan on every push or pull request. It fails the job above the fail-on threshold, uploads the SARIF report to the Security tab and keeps a single pull request comment up to date.

yaml
# .github/workflows/security.yml
name: Security Scan

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  pull-requests: write      # pull request comment
  security-events: write    # SARIF upload to the Security tab

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - name: SecuProbe Security Scan
        id: scan
        uses: Auxilh/secuprobe-scan-action@v1.1.0
        with:
          api_key: ${{ secrets.SECUPROBE_API_KEY }}
          url: https://staging.your-app.com
          fail-on: high   # critical | high | medium | low | none

      - name: Print score
        if: always()
        run: |
          echo "SecuScore: ${{ steps.scan.outputs.secu_score }}/100"
          echo "Report: ${{ steps.scan.outputs.report_url }}"

Inputs: api_key (required), url (required), fail-on (default: critical), timeout (default: 600), comment (default: true), upload_sarif (default: true)

Outputs: scan_id, secu_score, report_url, sarif_file, new_findings, fixed_findings

All inputs and outputs of the GitHub Action

GitLab CI

Include the published template in your .gitlab-ci.yml. Store the API key in a masked CI/CD variable named SECUPROBE_API_KEY. To get the merge request comment, add a project access token with the api scope in SECUPROBE_GITLAB_TOKEN.

yaml
# .gitlab-ci.yml
include:
  - remote: 'https://secuprobe.io/ci/secuprobe.gitlab-ci.yml'

secuprobe:
  extends: .secuprobe-scan
  variables:
    SECUPROBE_URL: 'https://staging.your-app.com'
    SECUPROBE_FAIL_ON: 'high'   # critical | high | medium | low | none

The job fails when a finding reaches SECUPROBE_FAIL_ON, keeps the SARIF report as an artifact and updates one comment on the merge request, with the new findings, the fixed ones and a link to the report.

Bitbucket Pipelines

Add the SecuProbe pipe to bitbucket-pipelines.yml. Store SECUPROBE_API_KEY as a secured repository variable. To get the pull request comment, add a repository access token with pull request write access in SECUPROBE_BITBUCKET_TOKEN. Source: bitbucket.org/secuprobe/scan-pipe.

yaml
# bitbucket-pipelines.yml
pipelines:
  pull-requests:
    '**':
      - step:
          name: SecuProbe security scan
          script:
            - pipe: docker://secuprobe/scan-pipe:1.0.0
              variables:
                SECUPROBE_API_KEY: $SECUPROBE_API_KEY
                SECUPROBE_URL: 'https://staging.your-app.com'
                SECUPROBE_FAIL_ON: 'high'   # critical | high | medium | low | none
                SECUPROBE_BITBUCKET_TOKEN: $SECUPROBE_BITBUCKET_TOKEN
          artifacts:
            - secuprobe.sarif

Without the pipe, the same scan runs from the published script in any Alpine step:

yaml
# bitbucket-pipelines.yml
definitions:
  steps:
    - step: &secuprobe-scan
        name: SecuProbe security scan
        image: alpine:3.20
        script:
          - apk add --no-cache curl jq >/dev/null
          - export SECUPROBE_URL="https://staging.your-app.com"
          - export SECUPROBE_FAIL_ON="high"
          - curl -fsSL https://secuprobe.io/ci/secuprobe-scan.sh -o /tmp/secuprobe-scan.sh
          - sh /tmp/secuprobe-scan.sh
        artifacts:
          - secuprobe.sarif

pipelines:
  pull-requests:
    '**':
      - step: *secuprobe-scan
  branches:
    main:
      - step: *secuprobe-scan

GitLab and Bitbucket run the same script, published at https://secuprobe.io/ci/secuprobe-scan.sh. It needs curl and jq, and exits with 0 (passed), 1 (threshold reached), 2 (configuration or API error) or 3 (timeout).

Deploy hooks

A deploy hook starts a scan when your deployment finishes. Create one in Dashboard → Settings → Integrations: it is bound to one validated domain and has its own secret. Sign each call with HMAC-SHA256 over the timestamp, a dot and the raw body, and send the result in X-SecuProbe-Signature.

bash
TS=$(date +%s)
BODY='{"commit":"'"$COMMIT_SHA"'","environment":"production"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECUPROBE_HOOK_SECRET" | sed 's/^.* //')

curl -fsS -X POST https://secuprobe.io/api/hooks/deploy/HOOK_ID \
  -H "X-SecuProbe-Timestamp: $TS" \
  -H "X-SecuProbe-Signature: sha256=$SIG" \
  -d "$BODY"
json
{
  "scanId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "reportUrl": "https://secuprobe.io/scan/550e8400-e29b-41d4-a716-446655440000"
}

A call is rejected with 401 when the signature does not match, when the timestamp is more than five minutes away from the server clock, or when the hook is revoked. Replaying the same signed call does not start a second scan. The body is optional and cannot name another target.

Need help? Visit our support page or explore the integrations catalogue.

View pricing →