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_.
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.
| Plan | Requests / minute | API scans |
|---|---|---|
| Free | 10 | 10 (trial) |
| Starter | 30 | Unlimited |
| Pro | 60 | Unlimited |
| Business | 120 | Unlimited |
| Enterprise | 200 | Unlimited |
Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp).
Endpoints
Base URL: https://secuprobe.io/api/v1
/api/v1/scansscope: scans:createLaunch 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.
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" }'{
"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"
}/api/v1/scans/{id}scope: scans:readGet the status and summary of a scan. Poll until the status is 'completed' or 'failed'.
curl https://secuprobe.io/api/v1/scans/SCAN_ID \
-H "Authorization: Bearer sp_live_YOUR_API_KEY"{
"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-..."
}/api/v1/scans/{id}/vulnerabilitiesscope: scans:readList all vulnerabilities found in a scan, with descriptions and remediation guides.
curl https://secuprobe.io/api/v1/scans/SCAN_ID/vulnerabilities \
-H "Authorization: Bearer sp_live_YOUR_API_KEY"{
"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." }
}
]
}/api/v1/scans/{id}/summaryscope: scans:readGet 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.
curl "https://secuprobe.io/api/v1/scans/SCAN_ID/summary?fail-on=high" \
-H "Authorization: Bearer sp_live_YOUR_API_KEY"{
"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-..."
}/api/v1/scans/{id}/sarifscope: scans:readExport 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.
curl https://secuprobe.io/api/v1/scans/SCAN_ID/sarif \
-H "Authorization: Bearer sp_live_YOUR_API_KEY" \
-o secuprobe.sarif/api/v1/domainsscope: domains:readList all validated domains in your account.
curl https://secuprobe.io/api/v1/domains \
-H "Authorization: Bearer sp_live_YOUR_API_KEY"{
"domains": [
{
"id": "domain-uuid",
"hostname": "your-domain.com",
"isValidated": true,
"createdAt": "2026-01-15T10:00:00Z"
}
]
}Error codes
| HTTP code | Meaning | Action |
|---|---|---|
| 400 | Bad request | Check the request body and parameters |
| 401 | Unauthorized | Verify that the API key is valid and active |
| 403 | Forbidden | The action requires a higher plan |
| 404 | Not found | The resource does not exist or is not yours |
| 429 | Rate limited | Slow down and check the X-RateLimit-Reset header |
| 500 | Server error | Retry 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.
# .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
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.
# .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 | noneThe 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.
# 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.sarifWithout the pipe, the same scan runs from the published script in any Alpine step:
# 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-scanGitLab 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.
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"{
"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.