APIs
0-day streakReview

Your progress

Level 1

0 XP100 XP to level 2
0day streak
0best streak
0topics started
0cards reviewed
    Review cards

    Progress is stored only in this browser. No account is required.

    Layer 4 · Full breakdown · 20 min · 5 figures

    What is an API?

    An API is a contract that hides an implementation. Learn how a web API call is built, what the contract promises beyond its JSON shapes, the main API styles, and why a published API is so hard to change.

    An API (application programming interface) is a contract between two pieces of software. It lists the operations one side offers, what each one accepts and returns, how it fails, and what it promises about behaviour. The caller writes code against the contract, never against the code behind it, so either side can change as long as the contract holds. In system design, “the API” usually means a network API: the HTTP or gRPC calls that apps, partners and services make to each other.

    Picture it first: the wall socket

    Plug a phone charger into a wall socket and it works. You don’t know whether tonight’s power comes from a coal plant, a solar farm or a dam, and you don’t need to. The socket is the only thing you depend on.

    Look closely and the socket makes several kinds of promise:

    • A shape. Pins of a fixed size and spacing. Plugs built to that shape fit; others don’t.
    • Behaviour. A fixed voltage and frequency, so the charger knows what it will get.
    • Limits. A fuse rating. Draw too much current and the fuse trips, protecting the grid from you.
    • Identity. The meter knows whose account the power is billed to.

    Behind the wall, the utility can rebuild the grid and swap suppliers, and nothing in your house notices. But if it changed the socket’s shape overnight, every appliance in the country would stop working. That is why socket standards almost never change, and why travel adapters exist.

    Map it back: the socket is the API, the grid is the implementation (code, databases, servers), your appliances are clients, the fuse is a rate limit, the meter is authentication, and a travel adapter is an old API version kept running for callers who can’t upgrade yet.

    An API is a contract

    The key word is interface. An API separates two things:

    • Interface: what the caller can see and rely on. Operation names, inputs, outputs, errors and documented behaviour.
    • Implementation: everything that makes it work. The code, the language, the database, caches, how many servers run it, which other services it calls.

    Because the caller depends only on the interface, the provider can rewrite the implementation (move from MySQL to PostgreSQL, split a monolith, add a cache) without asking anyone, and the caller can be rewritten in any language without telling the provider. Two teams, or two companies, can move at their own speed.

    APIs exist at every layer of a computer:

    • Library and SDK APIs: function signatures inside one process. Python’s json.loads(text) promises to turn a JSON string into objects or raise an error.
    • Operating system APIs: system calls. POSIX read(fd, buf, count) reads up to count bytes from a file, pipe or socket, and your program never learns which driver did the work.
    • Browser (Web platform) APIs: fetch(), geolocation and local storage, implemented differently by each browser but identical to your JavaScript.
    • Network APIs: operations one process offers another across a network, usually over HTTP or gRPC. This is what a system design interview means by “the API”, and the focus of this lesson.

    Network APIs also differ by audience:

    • Public APIs are open to any developer who signs up, such as a payment gateway or a maps API.
    • Partner APIs serve a known set of businesses under an agreement: restaurants pushing menus to a delivery platform, or a bank connecting to a UPI switch.
    • Internal APIs connect your own services, such as the order service calling the pricing service. One user request often fans out into many of them, and nobody outside ever sees them.

    API vs protocol

    A protocol is a general set of rules for a conversation: HTTP, TCP, AMQP. An API is the specific set of operations one system offers, usually carried over a protocol. HTTP says what POST and 404 mean in general; a payments API says that POST /v1/payments asks a payer for money, and what a 404 from it implies.

    Anatomy of a web API call

    Take a UPI-style payments API that a shopping app calls at checkout. This request asks a customer to pay ₹249:

    POST /v1/payments HTTP/1.1
    Host: api.payquick.example
    Authorization: Bearer <access-token>
    Content-Type: application/json
    Idempotency-Key: 5f0c1e9a-2b7d-4c41-9a3e-7d2b8c6f1a04
    
    {"order_ref": "ORD-88213", "amount_paise": 24900, "currency": "INR", "payer_vpa": "asha@okbank"}

    And the server’s answer:

    HTTP/1.1 201 Created
    Location: /v1/payments/pay_9f3k2
    Content-Type: application/json
    
    {"id": "pay_9f3k2", "status": "pending", "amount_paise": 24900, "currency": "INR"}

    Read it part by part:

    1. Base URL and version. https://api.payquick.example/v1 names the API and its major version.
    2. Method. POST creates or acts; GET reads, PUT replaces, PATCH updates part, DELETE removes. Each makes promises about safety and retries (see HTTP and HTTPS).
    3. Path. /payments is a collection; /payments/pay_9f3k2 is one payment in it.
    4. Query parameters filter or page a read: GET /v1/payments?status=pending&limit=20 (see pagination).
    5. Headers carry metadata: who is calling (Authorization), how to parse the body (Content-Type), and a retry token (Idempotency-Key) so a retried POST can’t charge twice (see idempotency).
    6. Body. The input, in an agreed format: JSON here, Protocol Buffers in gRPC.
    7. Status code. 201 Created: it worked and a new resource exists.
    8. Response headers. Location says where the new payment lives.
    9. Response body. The payment as the server now sees it. Notice "status": "pending": no money has moved, because the payer still has to approve in their UPI app. The client polls GET /v1/payments/pay_9f3k2 or waits for a webhook.

    Status classes do most of the work

    RFC 9110 gives the first digit of a status code its meaning, and says a client must understand the class even when it doesn’t know the exact code: an unknown 471 is handled like a 400. That rule lets generic code (retry libraries, proxies, SDKs) behave sensibly:

    Class Meaning What a well-behaved client does
    2xx Success Use the body; 201 means created, 202 means accepted for later
    3xx Look elsewhere Follow Location, or reuse the cached copy on a 304
    4xx The request is wrong Fix it before sending again; only 408 and 429 invite a retry, after waiting
    5xx The server failed Retry with backoff, but only if the operation is idempotent

    The individual codes worth knowing are listed in HTTP and HTTPS.

    Errors are data too

    A status code tells you what class of thing went wrong, not what to do about it. RFC 9457 defines a standard error body, application/problem+json, so every endpoint fails the same way:

    HTTP/1.1 422 Unprocessable Content
    Content-Type: application/problem+json
    
    {
      "type": "https://api.payquick.example/problems/unknown-vpa",
      "title": "The payer's UPI ID was not found.",
      "status": 422,
      "detail": "asha@okbnk does not match any bank handle.",
      "instance": "/v1/payments/requests/req_51c7"
    }

    type is a stable URI that client code can branch on; title and detail are for humans; instance names this occurrence for a support ticket. The type is part of the contract. The wording of detail is not, though someone will parse it anyway.

    More than request shapes

    A JSON example shows the shape of a call. The contract callers really depend on is far bigger, and most bugs between teams live in the parts nobody wrote down.

    The layers of an API contract. The six on the left are promised; the implementation on the right is free to change, except for whatever callers have quietly started to rely on.Notice The dashed bottom layer: it was never promised, yet with enough callers it behaves like part of the contract.
    • Shapes: operations, field names and types, what is required, which error codes exist. This is the part an OpenAPI file captures well.
    • Semantics: what an operation does. Does POST /payments move money, or only ask the payer to approve? Is amount_paise in paise or rupees? Two systems can agree on every field and still disagree on meaning.
    • Guarantees: a retry with the same Idempotency-Key returns the first result instead of charging again; events for one payment arrive in order, or don’t; a GET right after a 201 sees the new payment, or may briefly 404 because reads hit a lagging replica. Callers code around these, so state them.
    • Limits: requests per second per key, maximum page size, maximum body size, how long results are kept. A caller who doesn’t know the limits discovers them in production as 429s and 413s.
    • Auth model: how a caller proves who it is and what it may touch. An API key identifies a calling application: simple, but a long-lived secret that usually carries the account’s full power. An OAuth 2.0 access token, sent as Authorization: Bearer …, is issued by an authorization server, expires quickly and carries scopes, so a user can let an app read their orders without letting it place them. See OAuth 2.0 and OpenID Connect.
    • Service level and lifecycle: expected latency and availability, support hours, how versions are named, and how much notice you get before something is removed.

    Everything outside the boundary is free to change, and that freedom is the payoff. Before a Flipkart-style sale, a team can add a cache, shard a database or rewrite a service in a week, and if the contract holds, not one caller notices.

    Write down the invisible parts

    When you sketch an API in an interview, add one line per endpoint for the promises that aren’t shapes: “idempotent with a key, kept 24 hours”, “list may lag writes by a couple of seconds”, “100 requests/s per merchant”. It shows you know where integrations actually break.

    API styles at a glance

    Every network API follows one of a few interaction shapes, and the shape matters more than the wire format. Who starts the conversation, and does anyone wait?

    Four interaction shapes. Request–response and streaming connect a client to a server directly; a broker and a webhook both decouple the two sides in time.Notice Only in request–response does the caller sit and wait for exactly one answer.
    Style How it talks Contract written in Reach for it when What you pay
    REST (resource-oriented HTTP + JSON) Request–response on URLs; HTTP methods as verbs OpenAPI Public and partner APIs; cacheable reads; curl must work Several calls per screen; fetching too much or too little
    RPC (gRPC) Named methods, binary Protobuf over HTTP/2; streams too .proto files Internal calls at high rates; strict types; deadlines Browsers need a proxy; harder to debug by eye
    GraphQL The client queries one endpoint, naming the fields it wants GraphQL schema (SDL) Many screens and clients wanting different slices of the same data HTTP caching is hard; queries need cost limits
    Events on a broker A producer publishes to a topic; consumers read when ready AsyncAPI, a schema registry Fan-out; decoupling teams; absorbing spikes Eventual consistency; duplicates and ordering
    Webhooks A provider POSTs an event to a URL the customer registered OpenAPI webhooks, an event catalogue Telling another company that something happened Retries, signatures, out-of-order delivery
    Real-time connections A long-lived WebSocket or SSE stream; the server pushes A message schema per channel Live tracking, chat, scores Millions of open connections to run
    API styles at a glance. Each is a default, not a rule; large systems use several at once.

    A Swiggy-style delivery platform uses most of these at once: REST for restaurant partners, a backend-for-frontend or GraphQL for the apps, gRPC inside, topics for order events, webhooks to partners, and a WebSocket for the rider’s live position. The first three are compared in REST vs GraphQL vs gRPC; push styles are in real-time connections and webhooks; and the wait-or-don’t-wait choice is in synchronous vs asynchronous communication.

    Writing the contract down

    A contract that lives only in a wiki drifts from the code within weeks. Machine-readable descriptions fix that:

    • OpenAPI for HTTP APIs: paths, parameters, bodies, responses and security schemes, in YAML or JSON. The current line is 3.2 (3.2.0 shipped in September 2025, patch 3.2.1 in September 2026), per the specification.
    • Protocol Buffers for gRPC: messages and service definitions in .proto files.
    • GraphQL SDL: the schema is the contract, and clients can query it.
    • AsyncAPI for event-driven APIs: channels, message payloads and who publishes what.

    Here is the payment lookup in OpenAPI:

    openapi: 3.2.0
    info: { title: PayQuick Payments, version: "1.4.0" }
    paths:
      /v1/payments/{id}:
        get:
          operationId: getPayment
          parameters:
            - { name: id, in: path, required: true, schema: { type: string } }
          responses:
            "200":
              description: The payment
              content:
                application/json:
                  schema: { $ref: "#/components/schemas/Payment" }
            "404":
              description: No payment with this id for this merchant
              content:
                application/problem+json:
                  schema: { $ref: "#/components/schemas/Problem" }

    And the same operation as a gRPC service:

    service Payments {
      rpc GetPayment(GetPaymentRequest) returns (Payment);
    }
    
    message GetPaymentRequest { string id = 1; }
    
    message Payment {
      string id = 1;
      int64 amount_paise = 2;
      string status = 3; // pending, succeeded, failed
    }

    One file like this feeds every consumer of the contract.

    Contract-first development. The team agrees the spec in review, and everything else is generated or checked from that one file.Notice The last two outputs are guards: they catch a breaking change before it ships, not after a client fails.
    • Code generation: server stubs that reject malformed requests before your code runs, and client SDKs that give callers typed methods instead of hand-built URLs.
    • Docs and mocks: reference docs, plus a mock server so the app team can build against the API before the backend exists.
    • Contract tests: in consumer-driven contract testing, each consumer publishes the requests it sends and the fields it reads, and the provider’s CI replays them against every build, so a provider learns it broke the Android app before release.
    • Breaking-change checks: CI diffs the new spec against the last released one and fails the build on a removed field or a new required parameter.

    This is API-first (or design-first) development: agree the contract, publish it, then build both sides in parallel. You gain parallel work and fewer integration surprises. You pay with design time up front and the discipline to keep the spec, not the code, as the source of truth.

    Why contracts are hard to change

    Once an API has callers, any behaviour they can observe is at risk of becoming part of the contract. Hyrum Wright, then a software engineer at Google, described it in what is now called Hyrum’s Law:

    “With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.”

    It happens quietly. A caller skips sorting because your list happened to come back newest first. A reconciliation script pattern-matches the detail text of an error. A partner stores your IDs in a 12-character column because every ID so far was 12 characters. A mobile app ships a 2-second timeout that was fine while your p99 was 400 ms. None of it was promised; all of it breaks when you change it. Wright calls this the implicit interface, and with enough users it matches the implementation exactly.

    Google’s API guidelines (AIP-180) split compatibility three ways. Source: old code still compiles against the new SDK. Wire: old clients still exchange bytes correctly with the new server. Semantic: old clients still get what a reasonable developer would expect. A safe change passes all three.

    Change Safe? Why
    Add a new endpoint or method Safe Old callers never call it
    Add an optional field to a response Safe, if clients ignore unknown fields Old clients skip it; new clients use it
    Add an optional request field whose default keeps the old behaviour Safe Old requests behave exactly as before
    Add a value to a response enum (refunded) Risky An old switch statement may crash or mishandle it; document that enums grow
    Add a required request field Breaking Every old request is now invalid
    Remove or rename a field Breaking To an old client, a rename is a removal
    Change a type or format ("249.00" to 24900, IPv4 to IPv6) Breaking Parsers and database columns downstream reject it
    Change meaning or units (rupees to paise) Breaking, and silent Nothing errors; amounts are 100× wrong
    Tighten validation or lower a limit Breaking Requests that passed yesterday fail today
    Change a default value Breaking Callers who relied on the default get new behaviour
    Paginate a list that used to return everything Breaking Old clients think the first page is the whole list
    One response, two changes, two clients. The old app was built before the change and can’t be updated today; the new app was built after it.Notice The new app works in both panels. Only the old client, which you can’t force to upgrade, decides whether a change is breaking.

    Two habits make most changes safe. Providers only add: new optional fields, new endpoints, new enum values announced in advance. Consumers are tolerant readers: they ignore unknown fields, don’t depend on undocumented ordering, and handle enum values they’ve never seen. Protobuf helps, because it identifies fields by number on the wire and skips numbers it doesn’t recognise, which is also why a deleted field’s number must never be reused. More in serialization and schema evolution.

    Versioning and deprecation

    When a breaking change is truly needed, give callers a way to opt in and time to move.

    Strategy Example You gain You pay
    Major version in the URL /v1/payments, later /v2/payments Visible in logs, docs and gateway routes; old and new run side by side Big-bang migrations; two code paths or a translation layer to maintain
    Version in a header, often a date Stripe-Version: 2026-09-30.endive Small, frequent breaking changes; each customer upgrades when ready The server must translate between many versions; invisible in URLs
    No versions, evolve only Deprecated GraphQL fields; internal protos No migrations for callers; one code path Old fields pile up; only works if you never need a true break
    Three ways to version an API. Most public APIs combine the first with strict additive rules inside each version.

    Google’s AIP-185 puts a major version in the URL path and the protobuf package, and only a major number: v1, never v1.1, because compatible changes ship in place. Old and new majors must work side by side for a transition period, and beta features should stay deprecated for 180 days before removal. Newer Google APIs can also pin a dated version with an X-Goog-Api-Version header.

    Stripe gives each account a default API version and lets a single request override it with the Stripe-Version header. Since the 2024-09-30.acacia release it publishes monthly versions with no breaking changes, plus a major release twice a year that may break things.

    Internal APIs rarely need any of this. You own every caller, so instead of shipping /v2 you add the new field, migrate callers, watch traffic on the old field fall to zero, and then delete it. That is the same expand-and-contract pattern used in online schema migrations.

    Retiring something public takes five steps:

    1. Announce it in the changelog and by email, with dates and a migration guide.
    2. Signal it in every response. RFC 9745 (March 2025) defines Deprecation: @1798761599, a Unix timestamp (here 31 December 2026, 23:59:59 UTC) for when the resource is or was deprecated, and a Link with rel="deprecation" pointing at the docs. RFC 8594 adds Sunset: Wed, 30 Jun 2027 23:59:59 GMT, the time it is expected to stop responding, which must not be earlier than the deprecation date. Both are hints: a deprecated resource keeps working until its sunset.
    3. Measure who still calls the old version, per API key, and contact them directly.
    4. Brown out: switch the old version off for short, announced windows so forgotten integrations fail loudly while there is still time.
    5. Remove it on the sunset date, returning a clear error such as 410 Gone.

    Going deeper: a network call is not a function call

    RPC frameworks make a remote call look like a local one. It isn’t, and a good API design absorbs the difference.

    Latency. A local function call returns in nanoseconds. A network call pays a round trip first: for the examples here, assume about 1 ms between services in one data centre and 80 ms from a phone on a mobile network (see numbers to know).

    Unknown outcomes. A function either returns or throws. A network call can also time out with no answer, and the payment may have gone through just as the connection dropped. The contract must say what is safe to retry, which is why write APIs take idempotency keys and clients use timeouts, retries and backoff with jitter.

    Chatty vs chunky. An order-tracking screen needs ten pieces of data (order, restaurant, rider, ETA and so on), and each request needs an ID from the one before.

    The same ten dependent lookups, made from the phone or behind one screen-shaped endpoint (illustrative numbers).Notice The dependency chain is identical in both rows; only the place where the round trips happen has moved.

    From the phone, one call at a time: 10 × 80 ms = 800 ms of waiting on round trips alone. Through one screen-shaped endpoint that makes the same calls server-side: 80 ms + 10 × 1 ms = 90 ms, about 9× faster, with fewer radio wake-ups on the phone. You pay with a bigger endpoint coupled to one screen, and more data than some clients need. That is why teams build a backend-for-frontend per app or use GraphQL; see API composition, BFF and GraphQL federation and the chatty I/O anti-pattern.

    Internal vs external APIs

    Internal (service to service) External (public or partner)
    Trust Authenticated service identity, often mTLS; still check permissions Assume hostile: validate everything, limit every key
    Compatibility horizon Weeks: you can find and fix every caller Years: some callers never upgrade
    Typical style gRPC with Protobuf, or events REST with JSON and OpenAPI, plus webhooks
    How it changes Expand, migrate callers, contract Versions, changelog, deprecation headers, sunset dates

    Gateways and API management

    Public APIs usually sit behind an API gateway that checks keys or tokens, applies per-key quotas and rate limits, and records usage per caller. Those per-key analytics are what make deprecation possible: you can’t retire v1 without knowing who still calls it.

    The API is a product

    For external developers, the API is the product. Good ones ship docs generated from the spec plus task guides, a sandbox with test credentials (test UPI IDs that always succeed or always fail), SDKs, a changelog, a status page, and error messages that say how to fix the problem. A useful single measure is time to a new developer’s first successful call.

    Security: authorization per object

    The OWASP API Security Top 10 (2023) ranks Broken Object Level Authorization (BOLA) first: an endpoint takes an object ID from the caller and never checks that this caller may see that object, so any valid key that requests GET /v1/payments/pay_9f3k2 gets another merchant’s payment. Others map straight onto contract layers: property-level authorization (API3), such as accepting "status": "succeeded" in a create request; unrestricted resource consumption (API4), meaning missing limits on page sizes, uploads or OTP sends; and improper inventory management (API9), such as a forgotten v1 still running without the new checks.

    Authentication is not authorization

    A gateway can prove who is calling. Only the service that loads the payment can check that this caller owns this payment, so make that check on every request that takes an ID, in the service, against the owner stored with the object.

    Cheat sheet

    Idea Remember it as
    API A contract: operations, inputs, outputs, errors and guarantees, hiding the implementation
    A web call Method + path + query + headers + body → status + headers + body
    Status classes 2xx done, 3xx look elsewhere, 4xx fix the request, 5xx retry only if idempotent
    Errors application/problem+json: type, title, status, detail, instance (RFC 9457)
    Beyond shapes Semantics, guarantees, limits, auth, service level, lifecycle
    Styles REST at the edge, gRPC inside, GraphQL for many screens, events to decouple, webhooks to notify, WebSockets or SSE for live data
    Describe it OpenAPI, .proto, GraphQL SDL, AsyncAPI → codegen, docs, mocks, contract tests
    Hyrum’s Law Every observable behaviour will be depended on by somebody
    Safe changes Add optional fields and endpoints; clients ignore what they don’t know
    Breaking changes Remove, rename, retype, change meaning or defaults, tighten validation, add a required input
    Versioning /v1 in the path, or a dated header (Stripe); internally, expand and contract
    Retiring Deprecation (RFC 9745) + Sunset (RFC 8594) + per-key usage + brownouts
    Network ≠ function Round trips, unknown outcomes; 10 × 80 ms = 800 ms vs one 90 ms call
    Top API risk BOLA: check the caller may access this object, on every request

    How to say it in the interview

    “An API is a contract: the operations a system offers, their inputs and outputs, its errors, and promises like idempotency, limits and latency. It hides the implementation, so I can change the database or split the service without breaking anyone. Here I’d expose REST with JSON to apps and partners, described in OpenAPI, and use gRPC between internal services. Writes take an idempotency key, errors use problem details, and lists are cursor-paginated. I’d put /v1 in the path, make only additive changes inside it, and retire anything with Deprecation and Sunset headers, because by Hyrum’s Law, once clients depend on behaviour, they depend on all of it.”

    What makes a change breaking? Anything an unmodified existing client can observe going wrong: removing, renaming or retyping a field, adding a required input, changing meaning, units or defaults, or tightening validation. Adding optional fields and new endpoints is safe as long as clients ignore what they don’t recognise.

    How would you version this API? For public callers, a major version in the path with only additive changes inside it, and a new major only for a true break, run side by side and retired with deprecation headers and per-key tracking. For internal APIs, no versions at all: expand, migrate the callers, then contract.

    Why not just expose the database? Then your schema becomes the contract. You could never rename a column, shard, add a cache or change engines without breaking callers, and you couldn’t enforce business rules, limits or per-object authorization in one place. The API is the seam that lets the inside change.

    What’s the difference between an API and a protocol? A protocol such as HTTP or TCP defines how any two parties talk. An API is one system’s specific operations and promises, carried over a protocol.

    How do internal APIs differ from public ones? You control every internal caller, so you can change contracts in weeks with expand-and-contract, favour gRPC, and trust authenticated service identities. Public callers are unknown, untrusted and slow to upgrade, so they need explicit versions, strict validation, per-key limits and long deprecation windows.

    Where to go next

    References and further reading

    1. RFC 9110 — HTTP Semanticsrfc-editor.orgStatus code classes, and the rule that a client must treat an unknown code as the x00 code of its class.
    2. RFC 9457 — Problem Details for HTTP APIsrfc-editor.orgThe application/problem+json error body with type, title, status, detail and instance; replaces RFC 7807.
    3. OpenAPI Specification v3.2.1spec.openapis.orgThe language-agnostic description format for HTTP APIs; its revision history dates 3.2.0 to September 2025 and 3.2.1 to September 2026.
    4. Hyrum’s Law (Hyrum Wright)hyrumslaw.comThe original wording of the law and the idea of an implicit interface that grows with the number of users.
    5. Google AIP-180 — Backwards compatibilitygoogle.aip.devSource, wire and semantic compatibility, and a catalogue of changes that break callers: renames, type changes, new required fields, changed defaults.
    6. Google AIP-185 — API versioninggoogle.aip.devMajor version in the URL path (v1, never v1.1), side-by-side versions during migration, a recommended 180-day beta deprecation, and date-based versions in a header.
    7. Stripe API reference — Versioningdocs.stripe.comAccount-pinned API versions, the Stripe-Version override header, and monthly non-breaking releases plus twice-yearly major releases since 2024-09-30.acacia.
    8. RFC 9745 — The Deprecation HTTP Response Header Fieldrfc-editor.orgThe 2025 Deprecation header (a structured-field date), the deprecation link relation, and its relationship to Sunset.
    9. RFC 8594 — The Sunset HTTP Header Fieldrfc-editor.orgAn HTTP-date after which a resource is expected to stop responding, treated by clients as a hint.
    10. OWASP API Security Top 10 — 2023owasp.orgThe ten most common API risks, led by Broken Object Level Authorization (API1:2023).