Common API Errors
This page explains the most common HTTP error responses returned by the NAT API, their causes, and how to fix them.
401 Unauthorized
Error code: UNAUTHORIZED
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key."
}Causes
- The
X-API-Keyheader is missing from the request - The API key is invalid, expired, or belongs to a deleted tenant
- The key was copied incorrectly (extra whitespace, truncated)
Fix
- Ensure the
X-API-Keyheader is present on every request:curl https://api.nat-testing.io/api/v1/usage \ -H "X-API-Key: nat_pk_your_api_key_here" - Verify the key starts with
nat_pk_and matches what is shown in the dashboard (opens in a new tab) under Settings → API Keys. - Do not use
Authorization: Bearer— NAT usesX-API-Keyexclusively. - If the key was revoked, generate a new one from the dashboard.
Never commit your API key to source control. Store it as an environment variable or in a secrets manager and reference it at runtime.
402 Payment Required
Error code: QUOTA_EXCEEDED
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"error": "QUOTA_EXCEEDED",
"message": "Monthly scan quota exhausted. Quota resets on 2025-02-01.",
"quota_reset_date": "2025-02-01T00:00:00Z"
}Causes
- Your monthly functional or security scan quota is fully consumed
- You are on the Free plan and have used all 50 functional scans for this period
Fix
Option 1 — Wait for the reset date. Quota resets automatically on the first day of each billing period. Check your reset date:
curl https://api.nat-testing.io/api/v1/usage \
-H "X-API-Key: $NAT_API_KEY"The quota_reset_date field contains the exact ISO 8601 timestamp.
Option 2 — Upgrade your plan for an immediate quota increase:
curl -X POST https://api.nat-testing.io/api/v1/billing/checkout \
-H "X-API-Key: $NAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan": "pro",
"success_url": "https://app.nat-testing.io/dashboard?upgraded=true",
"cancel_url": "https://app.nat-testing.io/dashboard"
}'Or upgrade directly via the dashboard (opens in a new tab) under Settings → Billing → Upgrade Plan.
In-flight scans are not interrupted when quota is exhausted — only new scan requests are blocked. See Pricing & Plans for quota details by plan.
429 Too Many Requests
Error code: RATE_LIMITED
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 12
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 12 seconds.",
"retry_after": 12
}Causes
- You have exceeded your plan's per-minute request rate limit
- Automated polling loops running too aggressively
Fix
- Respect the
Retry-Afterheader. Back off for the number of seconds specified before retrying. - Reduce polling frequency. When polling scan status, use a reasonable interval (5–10 seconds) rather than tight loops.
- Upgrade your plan for a higher rate limit:
| Plan | Requests/minute |
|---|---|
| Free | 30 |
| Pro | 120 |
| Team | 300 |
| Enterprise | 1,000 |
Example: exponential back-off in Python
import time
import requests
def poll_with_backoff(scan_id, api_key, max_retries=10):
base_url = "https://api.nat-testing.io/api/v1"
headers = {"X-API-Key": api_key}
delay = 5
for attempt in range(max_retries):
r = requests.get(f"{base_url}/scan/{scan_id}", headers=headers)
if r.status_code == 429:
retry_after = int(r.headers.get("Retry-After", delay))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
delay = min(delay * 2, 60)
continue
r.raise_for_status()
data = r.json()
if data["status"] in ("completed", "failed", "cancelled"):
return data
time.sleep(delay)
raise TimeoutError("Scan did not complete within max retries")503 Service Unavailable
Error code: STRIPE_NOT_CONFIGURED (self-hosted only)
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
{
"error": "STRIPE_NOT_CONFIGURED",
"message": "Billing is not configured on this server. Set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET."
}Causes
- You are self-hosting NAT and the Stripe environment variables are not set
- This error only appears on billing endpoints (
/api/v1/billing/*) in self-hosted deployments
Fix
Set the required Stripe environment variables on your server:
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_ID_PRO=price_...
STRIPE_PRICE_ID_TEAM=price_...See the Self-Hosted Setup guide for a full list of required environment variables.
On the NAT Cloud (SaaS), this error will never appear — Stripe is pre-configured. You will only encounter 503 STRIPE_NOT_CONFIGURED when running a self-hosted NAT instance without billing configured.
Other errors
| Status | Code | Description |
|---|---|---|
400 Bad Request | INVALID_REQUEST | Malformed JSON or missing required fields. Check the request body. |
403 Forbidden | FEATURE_NOT_AVAILABLE | Feature not available on your current plan (e.g., security scans on Free). Upgrade to access. |
404 Not Found | NOT_FOUND | The scan ID does not exist or belongs to another tenant. |
500 Internal Server Error | INTERNAL_ERROR | Unexpected server error. Retry after a short delay. If it persists, contact support. |
See also
- API Quickstart — full onboarding flow with error handling examples
- Scan API reference — rate limit headers and quota error codes
- Usage & Quotas API — check your current quota and reset date
- Pricing & Plans — compare plan limits and upgrade options
- Billing Issues — webhook and checkout troubleshooting