API documentation
The portscan.com API checks open TCP ports on a server programmatically. No API key, no authentication, and no signup required. The scan target is always the IP address of the server making the API request, determined from the connection itself. This makes the API suitable for servers checking their own port exposure, CI/CD pipelines verifying firewall rules, and monitoring scripts confirming expected services are reachable.
Both IPv4 and IPv6 are supported. Port 25 (SMTP) is included in every scan, unlike most cloud-hosted alternatives where outbound port 25 is blocked.
Quick start
Run from the server to be scanned:
# Start a fast scan curl -X POST https://api.portscan.com/v1/fast # Poll for results curl https://api.portscan.com/v1/fast
The API scans the IP address the curl request originates from. Running these commands from a local workstation scans the workstation’s public IP. Running them from a cloud server scans the server’s IP.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /v1/fast | Start a fast scan (30 ports) |
| POST | /v1/deep | Start a deep scan (65,535 ports) |
| GET | /v1/fast | Poll fast scan results |
| GET | /v1/deep | Poll deep scan results |
Scan duration
Typical wall-clock time after the scan actually starts is about 8 to 20 seconds for a fast scan and about 30 to 60 seconds for a deep scan. When the scanner queue is busy, extra waiting time can occur between POST and the start of scanning.
The eta_seconds field in the
queued response is an
indicative hint for polling
intervals, not a guarantee of total time
from POST to completion. Poll until
status is
complete or
failed.
Responses
200 Scan submitted
Returned by POST when a scan is queued.
{
"ip": "203.0.113.42",
"scan_type": "fast",
"status": "queued",
"eta_seconds": 8
}
eta_seconds is a rough lower
bound for the scanning phase; real-world
duration often falls in the ranges in
Scan duration above.
200 Scan complete
Returned by GET when results are ready.
{
"ip": "203.0.113.42",
"scan_type": "fast",
"status": "complete",
"ports_open": [
{"port": 22, "state": "open", "service": "SSH", "banner": null},
{"port": 80, "state": "open", "service": "HTTP", "banner": null},
{"port": 443, "state": "open", "service": "HTTPS", "banner": null}
],
"ports_filtered": [],
"service_banners": {},
"ptr_record": "mail.example.com",
"duration_ms": 6231,
"completed_at": 1711940665,
"expires_at": 1712027200
}200 Scan failed
Returned by GET when a scan did not complete.
{
"ip": "203.0.113.42",
"scan_type": "deep",
"status": "failed",
"chunks_complete": 28,
"total_chunks": 32,
"fail_reason": "timeout"
}200 No scan found
Returned by GET when no scan exists for this IP.
{
"ip": "203.0.113.42",
"scan_type": "fast",
"status": "none"
}Error responses
| Status | Error | Description |
|---|---|---|
| 403 | not_available | IP is opted out or restricted |
| 409 | conflict | Scan already running for this IP |
| 422 | invalid_target | IP is RFC 1918, loopback, or otherwise non-routable |
| 429 | rate_limited | Too many requests. Check Retry-After header. |
Response headers
| Header | When | Value |
|---|---|---|
| Retry-After | On 429 responses | Seconds until rate limit resets |
| X-RateLimit-Limit | All responses | Maximum requests in the current window |
| X-RateLimit-Remaining | All responses | Requests remaining in the current window |
| X-RateLimit-Reset | All responses | Unix timestamp when the rate limit resets |
| X-Scan-Duration-Ms | Completed scan GET responses | Scan duration in milliseconds |
Webhook
Pass a URL as the X-Callback-Url header to receive a POST notification when the scan completes or fails.
curl -X POST https://api.portscan.com/v1/fast \ -H "X-Callback-Url: https://myserver.com/hook"
The webhook fires on both completion and failure. Webhook and polling are fully complementary and work regardless of whether a callback URL was provided.
Rate limits
| Scope | Limit | Window |
|---|---|---|
| Fast scan | 200 per ASN | 1 hour |
| Deep scan | 1 per IP, 20 per ASN | 5 minutes (IP), 1 hour (ASN) |
| Edge limit | 30 requests per IP | 1 minute |
Rate-limited responses return HTTP 429 with a Retry-After header.
Common use cases
Server hardening check. After configuring a firewall, run a fast scan from the server to confirm only intended ports are open. Add the curl command to a post-deployment script.
Mail server verification. Check whether port 25 is open and reachable from outside the network. Most cloud-hosted scanners block port 25, making this test impossible from those platforms.
CI/CD integration. Add a port scan step to deployment pipelines. POST to start a scan, poll until complete, and fail the pipeline if unexpected ports are found open.
Monitoring. Periodically check a server’s port exposure with a cron job. Use the webhook callback to receive results without polling.
Limitations
The API scans the IP of the requesting server only. There is no parameter for specifying a different target IP. This is enforced at the server level and cannot be bypassed. The API is designed for self-checking, not for scanning third-party infrastructure.
UDP scanning is not supported. All scans are TCP only.
Scan results expire after 24 hours. Poll for results within this window.
Terms and policies
Usage of the API is subject to the portscan.com Terms of Service and Privacy Policy. For abuse reports or questions, see the Abuse contact page. To opt out an IP or range, visit Opt-out.