high9 min readLast updated May 27, 2026

Exposed API Endpoints: How Unsecured APIs Become Your Biggest Attack Vector

Exposed API endpoints without authentication or rate limiting are a top attack vector. Learn how to discover, audit, and secure your APIs before attackers do.

Why API security matters now more than ever

APIs are the backbone of modern applications. Every mobile app, single-page application, microservice, webhook, and third-party integration communicates through APIs. And unlike traditional web applications, APIs are designed to be accessed programmatically -- which makes them trivially easy to probe and exploit at scale.

The problem is that many APIs are deployed with minimal security. Development teams focus on functionality and ship fast, leaving authentication, authorisation, rate limiting, and input validation as afterthoughts. The result: APIs that leak data, accept unauthenticated requests, or expose internal functionality to the internet.

Common ways APIs end up exposed

Forgotten development and staging APIs

A staging API deployed for testing at api-staging.yourcompany.com is left running after the project launches. It has relaxed authentication, verbose error messages, and debug endpoints. Attackers find it through certificate transparency logs or subdomain enumeration.

APIs with no authentication

Internal microservices that were never intended to be internet-facing get exposed through a misconfigured load balancer or security group. Because they were "internal only," they have no authentication layer.

Default API documentation endpoints

Many frameworks ship with auto-generated documentation that is enabled by default:

  • /swagger-ui/ or /swagger.json (Swagger/OpenAPI)
  • /api-docs or /docs (FastAPI, various frameworks)
  • /graphql with introspection enabled (GraphQL)
  • /_explorer (LoopBack)

These documentation endpoints reveal every available endpoint, parameter, and data type -- a complete API map for attackers.

Overly permissive CORS

Cross-Origin Resource Sharing (CORS) headers that allow * or reflect any origin let any website make authenticated requests to your API on behalf of your users. See CORS misconfiguration for details.

API keys in client-side code

API keys embedded in JavaScript, mobile apps, or public repositories provide direct access to your API. Unlike passwords, API keys are often not rotated and may have broad permissions.

OWASP API Security Top 10

The OWASP API Security Top 10 is the definitive list of API security risks. The most commonly seen issues:

Broken Object Level Authorisation (BOLA)

An API that lets you access /api/users/123/orders does not check whether you are user 123. By changing the ID to 124, 125, 126, you can access other users' data. This is the single most common API vulnerability.

Broken Authentication

APIs that use weak authentication mechanisms, accept tokens that never expire, do not validate token signatures, or allow credential stuffing without rate limiting.

Broken Object Property Level Authorisation

An API returns more data than the client needs. The user profile endpoint returns not just the display name and avatar, but also the email, phone number, internal user ID, role, and account creation date.

Unrestricted Resource Consumption

APIs without rate limiting allow attackers to make millions of requests, brute-forcing credentials, scraping data, or causing denial of service.

Server-Side Request Forgery (SSRF)

APIs that accept URLs as input (for webhooks, image processing, URL previews) can be tricked into making requests to internal services, cloud metadata endpoints, or other sensitive resources.

How to discover your exposed APIs

Check certificate transparency logs

APIs hosted on subdomains like api., api-v2., graphql., gateway. appear in CT logs:

curl -s "https://crt.sh/?q=%25.yourcompany.com&output=json" | \
  jq -r '.[].name_value' | sort -u | grep -i api

Scan common API paths

# Common API documentation endpoints
for path in /swagger-ui/ /swagger.json /api-docs /docs /graphql /openapi.json /_explorer /v1 /v2 /api/v1 /api/v2; do
  STATUS=$(curl -so /dev/null -w "%{http_code}" "https://yourcompany.com$path")
  echo "$path: $STATUS"
done

Check for exposed GraphQL introspection

curl -s -X POST https://api.yourcompany.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __schema { types { name } } }"}' | head -50

If this returns your schema, introspection is enabled and attackers can map your entire API.

Internet scanners

Shodan and Censys index API responses. Search for your organisation to see what is publicly visible.

Securing your APIs

Authentication

Every API endpoint that is not explicitly public must require authentication:

  • OAuth 2.0 / OIDC -- the standard for user-facing APIs
  • API keys -- acceptable for server-to-server communication, but must be transmitted in headers (never URLs) and rotated regularly
  • JWT tokens -- verify signatures, check expiry, validate the issuer and audience claims
  • Mutual TLS (mTLS) -- for high-security service-to-service communication
# Always require authentication headers
# Reject requests without valid credentials with 401
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Authorisation

Authentication proves who you are. Authorisation determines what you can access. Implement authorisation checks at the object level:

# WRONG: only checks if user is authenticated
@app.get("/api/users/{user_id}/data")
def get_user_data(user_id: int, current_user: User):
    return db.get_user_data(user_id)

# RIGHT: checks if the authenticated user can access this specific resource
@app.get("/api/users/{user_id}/data")
def get_user_data(user_id: int, current_user: User):
    if current_user.id != user_id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Forbidden")
    return db.get_user_data(user_id)

Rate limiting

Implement rate limiting at multiple levels:

  • Per IP -- prevent individual IPs from overwhelming the API
  • Per user/token -- prevent compromised credentials from being used for bulk data extraction
  • Per endpoint -- stricter limits on sensitive endpoints (login, password reset, search)
# Nginx rate limiting for API endpoints
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
limit_req_zone $http_authorization zone=api_user:10m rate=100r/m;

location /api/ {
    limit_req zone=api burst=50 nodelay;
    limit_req zone=api_user burst=20 nodelay;
    limit_req_status 429;
}

For more on Nginx rate limiting, see our Nginx security hardening guide.

Input validation

Validate all input on the server side. Never trust client-side validation:

  • Type checking (is this really an integer?)
  • Length limits (is this string a reasonable length?)
  • Format validation (is this a valid email address?)
  • Range checking (is this quantity between 1 and 100?)
  • Allowlisting (is this status value one of the permitted options?)

Disable API documentation in production

# FastAPI -- disable docs in production
import os
from fastapi import FastAPI

app = FastAPI(
    docs_url=None if os.getenv("ENV") == "production" else "/docs",
    redoc_url=None if os.getenv("ENV") == "production" else "/redoc",
    openapi_url=None if os.getenv("ENV") == "production" else "/openapi.json"
)

Disable GraphQL introspection in production

# Python (Ariadne)
from ariadne import make_executable_schema
schema = make_executable_schema(type_defs, query, introspection=False)
// Node.js (Apollo Server)
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
});

Response filtering

Return only the fields the client needs. Never expose internal IDs, debug information, stack traces, or database schemas in API responses.

// WRONG: exposes everything
{
  "id": 12345,
  "internal_id": "usr_abc123",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "password_hash": "$2b$12$...",
  "role": "admin",
  "created_at": "2024-01-15T10:00:00Z",
  "last_login_ip": "203.0.113.50"
}

// RIGHT: only what the requesting client needs
{
  "name": "Jane Doe",
  "email": "jane@example.com"
}

Security headers for APIs

APIs should return security headers just like web pages. See our guide on HTTP security headers. At minimum:

X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=63072000; includeSubDomains
Cache-Control: no-store
Content-Type: application/json

How SurfaceScan helps

SurfaceScan discovers API endpoints across your attack surface through subdomain enumeration, port scanning, and path probing. It detects exposed documentation endpoints (Swagger, GraphQL introspection), APIs responding without authentication requirements, and misconfigured CORS headers. Each finding includes the specific endpoint, what information it exposes, and remediation steps -- so your team can lock down exposed APIs before they become breach headlines.

Related articles