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 so mail reachability is visible in the same result set as the other ports—the status reflects what can be reached from outside, which matters for server-to-server SMTP and deliverability checks.
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 (32 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 around
10-20 seconds for a fast
scan and 30-90
seconds for a deep scan. When
multiple scans run in parallel, durations
grow accordingly — the
queue_position field in the
poll response indicates how many scans are
ahead.
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": 20
}
eta_seconds is a rough lower
bound for the scanning phase; real-world
duration is often near the Scan
duration targets above but can
vary with load and network conditions.
If a scan for this IP and scan type is
already in flight, POST returns the same
200 shape with status set to
queued or running
and chunks_complete /
total_chunks fields reflecting
the ongoing scan. Duplicate submissions
coalesce onto the existing scan rather than
starting a new one.
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 |
| 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 | 2000 per ASN | 1 hour |
| Deep scan | 1 per IP, 200 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. The result reflects the same view other mail servers get when delivering to the host.
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 client only. There is no parameter for specifying a different target IP. The IP address always comes from the requester’s connection, is enforced at the server level and cannot be bypassed. The API is for self-checking (a host or pipeline testing its own egress IP), 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.