← Insights
compliance

API Security for B2B Platforms

Your public API is the front door, propped open with weak auth, no rate limits, and verbose errors. Here is the attack surface and how to close each part of it

Your platform has an API. Customers integrate against it, partners build on it, and you put it in the docs as a selling point. That API is the front door to your entire system, and right now it's propped open. Weak authentication that checks who you are but not what you can touch. No rate limits, so anyone can hammer it as hard as they like. Error responses so verbose they hand attackers a map of your internals.

A UI has a human and a browser between the attacker and your data. An API has nothing. It's the rawest possible interface to your system, which is exactly why it's where you get hit.

The cost of an open door

APIs are now the leading attack surface for web-facing companies, and API-specific incidents have been climbing for years. The reason is structural: APIs expose business logic and data directly, in a format built for programmatic access, which is precisely the format an attacker wants. A single broken-authorization API endpoint can leak your entire customer database one record at a time, and because it's a legitimate authenticated endpoint, nothing looks wrong in your logs until someone notices the volume.

For a B2B platform the stakes compound. Your API often holds multiple customers' data behind the same endpoints, separated only by authorization logic. One flaw in that logic and customer A reads customer B's data — which is the multi-tenant breach that ends B2B companies. The average breach cost past $9M is the headline number; the relevant number for you is that a tenant-isolation failure in your API is a disclosable incident across your entire customer base at once.

Authentication is not authorization

The most common and most damaging API vulnerability is the conflation of these two questions. Authentication asks "who are you." Authorization asks "are you allowed to do this." An API that only answers the first is wide open.

The canonical break: GET /api/v1/accounts/8821/users. The request carries a valid API key, so authentication passes. But nothing checks whether that key's owner is allowed to read account 8821. Change the number, read another customer's users. This is broken object-level authorization, and it tops the API security risk lists because it's everywhere — teams build authentication carefully and then assume that an authenticated request is automatically a permitted one.

The fix is architectural. Every endpoint that touches a resource must verify, at the service boundary, that the authenticated principal has permission for that specific resource and that specific action. Object-level: can you touch this record? Function-level: can you call this operation at all? Both checks, enforced by a layer the individual endpoint author can't skip — not by a per-route if statement they have to remember to write. The remembering-to model fails the day you add the hundredth endpoint.

For B2B specifically, tenant isolation is the non-negotiable case: every query is scoped to the caller's tenant at the data layer, so even a buggy endpoint can't cross the tenant boundary. You build the isolation into the foundation, not into each query's good intentions.

Rate limiting, the control everyone skips

No rate limiting means three problems at once. An attacker can brute-force credentials and API keys without friction. A single client can exhaust your resources and take the platform down for everyone — accidentally or on purpose. And the broken-authorization enumeration above runs at full speed; nothing stops someone from walking every record ID in minutes.

Rate limiting is the cheapest high-value API control and it's routinely absent because it doesn't show up in a demo. Do it in layers:

per-key      → each API key gets a request budget per window
per-endpoint → expensive operations get tighter limits
per-tenant   → no single customer can starve the others
global       → a backstop ceiling protects the platform

Tie the limits to your pricing tiers so they double as a product control. Return a clear 429 with a Retry-After header so legitimate clients can back off gracefully — rate limiting should shape good behavior, not just punish bad. And rate-limit authentication endpoints hardest of all, because that's where credential attacks land.

Input validation at every boundary

An API accepts input from clients you don't control, which means every field is hostile until proven otherwise. Unvalidated input is how injection, traversal, and a dozen other attacks get in. The discipline: validate every input against a strict schema at the API boundary, and reject anything that doesn't match before it reaches your business logic.

Strict means allow-list, not block-list. Define the exact shape you accept — types, ranges, lengths, formats — and refuse everything else. Don't try to enumerate bad input; enumerate good input and reject the rest. A field expecting an integer ID rejects a string. A field expecting an email validates the format. A pagination limit caps at a maximum so nobody requests a million records in one call. Mass-assignment is closed: a client can't set fields it shouldn't by stuffing extra keys into the payload, because the schema only binds the fields you allow.

This also closes the resource-exhaustion vector where a caller requests enormous result sets or deeply nested queries to overload the backend. Bounds on everything: page sizes, query depth, payload size, array lengths.

Error hygiene: stop narrating your internals

Verbose errors are a gift to attackers and a habit from development. A stack trace returned to the client reveals your framework, your file structure, your library versions, and sometimes your database schema or a connection string. A detailed authorization error — "user not permitted to access account 8821" versus a generic 404 — confirms that account 8821 exists, which is half of an enumeration attack.

The rule: clients get generic errors, your logging system gets detailed ones. Never the reverse. A failed request returns a clean, generic message and a correlation ID. The full detail — stack trace, context, the actual reason — goes to your internal logs keyed to that ID, where your team can find it and an attacker can't.

to the client:  { "error": "request failed", "id": "req_a91f" }
to your logs:    full stack trace + context, keyed to req_a91f

Specifically: return 404 rather than 403 where existence itself is sensitive, so you don't confirm which resources exist. Don't leak whether an account, email, or record exists through differential error messages. And strip all framework debug output in production — a single leaked stack trace can hand over more recon than hours of scanning.

The rest of the attack surface

A few more surfaces that round out the picture, briefly because they're standard once you know to look:

Versioning and deprecated endpoints. That v1 endpoint you forgot to retire still works and still has the old vulnerabilities. Old API versions are a classic forgotten door. Inventory every exposed endpoint and version; retire what you don't support.

CORS and exposure. A wildcard CORS policy lets any site call your API with the user's credentials. Scope it to the origins you actually serve.

Secrets in the API surface. API keys in URLs end up in logs and browser history. Keys go in headers. And keys are secrets — they belong in a store with rotation, covered in secrets management done right.

Logging and detection. Log every authentication and authorization decision so the enumeration attack that walks your endpoints shows up as a pattern — a single key generating thousands of authorization failures — and alerts you while it's happening instead of after the disclosure.

What fixed looks like

Every endpoint enforces both object-level and function-level authorization at the service boundary, and tenant isolation is built into the data layer so no query can cross a customer boundary. Rate limits run in layers — per key, per endpoint, per tenant, global — return clean 429s, and hit authentication hardest. Every input is validated against a strict allow-list schema before it reaches business logic, with bounds on page sizes, payloads, and query depth.

Errors to clients are generic with a correlation ID; the detail lives in your logs. Existence-sensitive endpoints return 404, not differential errors. CORS is scoped, keys are in headers and in a managed store, and old endpoint versions are retired. Authorization decisions are logged and anomalies alert. When a customer's security team probes your API — they will — it holds. You can see API security built into a real multi-tenant system in the Compliance-Ready SaaS engagement.

This is for you if

You run a B2B platform with a public or partner-facing API holding multiple customers' data, and you suspect the API grew faster than its security — authorization by convention, no rate limits, errors that say too much. You want it hardened before a tenant-isolation flaw turns into a breach across your whole customer base.

This is hardening work starting around $50k for a focused API security pass on an existing platform, and a core part of larger build engagements ($100k+) where the API is designed secure from the start.

It's not for internal-only APIs behind a private network with no external exposure, where the threat model is genuinely different. And it's not for teams that want a scan report filed and forgotten. The authorization layer and tenant isolation are architecture — they get built into the system or they don't protect it.