Concepts
What you will learn: When a mobile app or website talks to a backend, their contract must define request shape, outcomes and error handling. We will design resources, methods and status codes, then cover retries, pagination, versioning and live communication, balancing usability with compatibility and security.
Before you start: A client is the program requesting a service; a server receives and handles that request. An HTTP method such as GET or POST describes the action, and a status code summarizes the result. If HTTP is new, begin with the card and follow its examples.
Select a term with a dotted underline for its meaning, then continue reading from the same place.
#REST constraints and resource design
Several clients need a stable, understandable service interface. is an architectural style built around resources and their representations rather than arbitrary remote method names.
A store catalog gives every product an identifier and a known address without exposing warehouse operations.
- Client-server: separate presentation from service responsibilities.
- Stateless: each request supplies the interaction context; resource state is allowed, but hidden conversational state must not be required.
- Cacheable: declare cache rules with Cache-Control and validators such as ETag.
- Uniform interface: resource identification, manipulation through representations, self-descriptive messages, and hypermedia-driven state transitions.
- Layered system: gateways and proxies can intervene without redesigning clients.
- Code on demand: executable code delivery is optional.
GET /api/orders?status=paid&sort=-createdAt&limit=20 HTTP/1.1
GET /api/orders/42 HTTP/1.1
GET /api/customers/7/orders HTTP/1.1
POST /api/orders HTTP/1.1
POST /api/orders/42/cancellations HTTP/1.1
Use plural nouns and shallow nesting for real relationships. The last request models cancellation as a resource; a documented action such as /orders/42/cancel is also pragmatic. alone does not make an RESTful, and not every business operation fits simple .
#HTTP methods: safety and idempotency
Clients and intermediaries need to distinguish reads from changes and understand retry semantics. Safe means no state change is requested; incidental logging is allowed. means repeated identical requests have the same intended server effect, not necessarily identical responses.
| Method | Safe | Idempotent | Use |
|---|---|---|---|
| GET / HEAD | Yes | Yes | Representation or headers only |
| OPTIONS | Yes | Yes | Communication capabilities |
| POST | No | Not guaranteed | Create or process |
| PUT | No | Yes | Replace at a known |
| PATCH | No | Operation-dependent | Partial update |
| DELETE | No | Yes | Remove the resource association |
PUT /api/orders/42 HTTP/1.1
If-Match: "version-7"
Content-Type: application/json
{ "status": "confirmed", "deliveryNote": "Leave at reception" }
PUT replaces the agreed writable representation, not necessarily server-generated fields. If-Match rejects a stale version with 412. A PATCH setting a fixed status can be idempotent; incrementing quantity is not. DELETE can return 204 first and 404 later while remaining idempotent.
#Status codes and ProblemDetails
Returning 200 for every outcome makes clients guess. Agree on meaningful status codes and a stable error representation.
| Code | Practical meaning |
|---|---|
| 200 / 201 / 202 / 204 | Success with content; created with Location; accepted but unfinished; success without content |
| 304 | Cached representation remains valid after a conditional GET |
| 400 / 422 | Invalid request; or understood content that cannot be processed. Document validation policy |
| 401 / 403 / 404 | Valid authentication needed; authorization refused; missing or deliberately concealed |
| 409 / 412 | State conflict; failed precondition such as If-Match |
| 429 / 500 / 503 | Rate limit; unexpected failure; temporary unavailability. Retry-After helps with 429 and 503 |
builder.Services.AddProblemDetails(); // (1)
var app = builder.Build();
app.UseExceptionHandler(); // (2)
app.MapGet("/example-conflict", () => Results.Problem(
statusCode: 409, title: "Order state conflict",
type: "https://api.example.com/problems/order-conflict",
extensions: new Dictionary<string, object?>
{
["code"] = "order.conflict" // (3)
}));
- (1) Registers ProblemDetails support; 9457 defines the application/problem+json format.
- (2) Handles unexpected exceptions. Add known-error mappings and a traceId for log correlation.
- (3) A stable code supports clients; title and detail help people. Never expose stack or secrets.
202 needs a monitoring mechanism such as an operation resource in Location. 204 and 304 carry no response content. A 401 uses an appropriate WWW-Authenticate challenge.
#Idempotency keys for payments and retries
- A payment succeeds on the server, but its response is lost. The client cannot tell whether it succeeded and retries.
- The client reuses an operation key. The server durably associates that key with the request and result, returning the earlier result rather than charging again.
- Concurrent duplicates and reuse with different payloads also need handling. A Dictionary in one server process does not protect multiple instances or survive restarts.
A payment can succeed while its response is lost. A retry must reuse the logical operation's key to avoid charging twice.
-- SQL Server: one durable claim per caller, operation and key. (1)
CREATE UNIQUE INDEX UX_Idempotency_Claim
ON IdempotencyRecords (CustomerId, Operation, IdempotencyKey);
-- Store RequestHash, State, StatusCode, ResponseBody and ExpiresAt. (2)
-- Commit the claim, order change and stored response atomically. (3)
- (1) The unique constraint resolves competing requests; a SELECT before INSERT alone is unsafe.
- (2) Reject the same key with a different payload. Replay completed responses and document waiting or conflict behavior for work in progress.
- (3) Commit local changes together. External payments need a stable provider key, an and reconciliation; a database cannot include an call.
#Pagination, filtering and sorting
Unbounded order lists overload databases and networks. Offset supports numbered pages but traverses many rows for deep pages. A cursor continues after the last observed sort key.
var page = await db.Orders.AsNoTracking()
.Where(o => o.CustomerId == customerId && o.Status == "Paid") // (1)
.Where(o => o.CreatedAt < afterDate
|| (o.CreatedAt == afterDate && o.Id < afterId)) // (2)
.OrderByDescending(o => o.CreatedAt).ThenByDescending(o => o.Id)
.Select(o => new { o.Id, o.CreatedAt, o.Total })
.Take(limit + 1).ToListAsync(ct); // (3)
- (1) Apply authorization scope before paging. Allowlist filter and sort fields and cap limit.
- (2) The next-page predicate matches descending order; a numeric Id breaks timestamp ties. Omit it on the first page.
- (3) One extra row detects hasMore; return only limit rows and encode the last returned row into the cursor.
Match indexes to filters and ordering. Validate opaque cursors and bind them to the same filters. Cursor paging is not a fixed snapshot when records change. Offset suits small lists and random page access; an exact total count has a separate cost.
#Versioning and compatibility
Old clients must survive new deployments. Choose a versioning policy before changing the contract.
| Strategy | Example | Trade-off |
|---|---|---|
| Path | //v2/orders | Visible in docs and routing; multiple URLs |
| Query | ?api-version=2 | Simple; include it in cache keys |
| Header | Api-Version: 2 | Stable ; gateways and caches must understand it |
| Media type | Accept: application/vnd.store.v2+json | Content negotiation; more tooling work and appropriate Vary |
| Usually breaking | Compatible under stated assumptions |
|---|---|
| Remove a field or change its type or meaning | Add a response field if clients ignore unknown fields |
| Make optional input required | Add optional input with a compatible default |
| Change errors or behavioral guarantees | Add an endpoint without changing existing ones |
| Add enum values for exhaustive clients | Add values when unknown-value handling is explicitly supported |
Publish a deprecation period and measure usage before removal. Sunset communicates a retirement date. Behavior can break clients even when schemas stay identical.
#OpenAPI, contract-first and spec-driven development
Correct code cannot fix a shared misunderstanding between frontend and backend. Contract-first starts with a reviewed interface contract. Spec-driven development goes further: requirements, acceptance criteria and failure cases guide implementation plans and tests.
openapi: 3.1.0
info: { title: Orders API, version: 1.0.0 }
paths:
/orders/{id}:
get:
operationId: getOrder
parameters:
- in: path
name: id
required: true
schema: { type: integer }
responses:
'200': { description: Order found }
'404': { description: Order not found }
This is a valid minimal contract; a real one adds schemas, security and errors. Review it, generate mocks and typed clients, and run diffs plus behavioral contract tests in . Generating OpenAPI from implementation is different from letting a contract guide design.
// Requires Microsoft.AspNetCore.OpenApi, version 10.x.
builder.Services.AddOpenApi(); // (1)
var app = builder.Build();
if (app.Environment.IsDevelopment())
app.MapOpenApi(); // (2)
- (1) Built-in document generation arrived in 9; .NET 10 defaults to OpenAPI 3.1.
- (2) Exposes a document endpoint, not an interactive . Define an authorization policy if production access is required.
| Common older setup | Current option |
|---|---|
| Swashbuckle in older templates | Built-in generation; Swagger UI or Scalar separately. Swashbuckle remains usable |
For events, AsyncAPI describes channels, messages, send and receive operations, and protocol bindings. It does not provide delivery guarantees: document ordering, retries and deduplication explicitly.
#HATEOAS and conditional requestsBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
A client with hard-coded state transitions might offer cancellation for a shipped order. Hypermedia supplies links to actions available in the current state, using agreed relation semantics.
{
"id": 42,
"status": "pending",
"links": [
{ "rel": "self", "href": "/api/orders/42" },
{ "rel": "cancel", "href": "/api/orders/42/cancellations", "method": "POST" }
]
}
This illustrative shape is not a standardized media type. Clients still need relation and payload semantics; links are neither authorization nor a guarantee that state is unchanged. reduces coupling but adds design and tooling work, so many implement only part of . ETag with If-None-Match enables 304 responses; If-Match protects updates against lost changes.
#Webhooks: signatures and reliable delivery
Constantly polling for payment events wastes requests. A webhook delivers an callback, but events may be duplicated, delayed or reordered.
using System.Security.Cryptography;
using System.Text;
// rawBody is the exact received byte array; secret is loaded securely.
byte[] prefix = Encoding.UTF8.GetBytes(timestamp + ".");
byte[] signed = new byte[prefix.Length + rawBody.Length];
prefix.CopyTo(signed, 0);
rawBody.CopyTo(signed, prefix.Length);
byte[] expected = HMACSHA256.HashData(secret, signed); // (1)
bool valid = CryptographicOperations.FixedTimeEquals(
expected, receivedSignature); // (2)
- (1) This example protocol signs a timestamp and raw bytes. Never deserialize and reserialize before verification; follow the provider's exact signature format.
- (2) Compare in constant time. Validate encoding and length, reject stale timestamps, and deduplicate event IDs. A valid signature alone does not prevent replay.
Persist to a durable inbox before returning a prompt 2xx, then process in a worker. Senders need exponential backoff with jitter, bounded retries, dead-letter handling and manual replay. Receivers need and out-of-order handling. Use and secret rotation; validate user-supplied callback destinations against .
#SSE, WebSockets, SignalR and long polling
Live order status needs a channel matched to message direction and volume.
| Choice | Direction | Fit and cost |
|---|---|---|
| Server to client | text stream; EventSource reconnects; useful for progress | |
| WebSocket | Bidirectional | Text or binary messages; application manages protocol and reconnection |
| SignalR | Bidirectional hubs | Framework with groups and transport fallback; scale-out needs planning |
| Long polling | Request waits for an event, then repeats | Broad compatibility; more headers and requests |
using System.Runtime.CompilerServices;
app.MapGet("/demo/progress", (CancellationToken ct) =>
TypedResults.ServerSentEvents(Progress(ct), eventType: "progress")); // (1)
static async IAsyncEnumerable<int> Progress(
[EnumeratorCancellation] CancellationToken ct)
{
for (int percent = 0; percent <= 100; percent += 10)
{
await Task.Delay(500, ct); // (2)
yield return percent;
}
}
- (1) 10's TypedResults.ServerSentEvents writes text/event-stream instead of hand-formatting frames.
- (2) The cancellable demonstration stream stands in for a real bounded source of updates.
Configure proxy buffering, timeouts and heartbeats. Replay requires event IDs, retained events and Last-Event-ID handling; reconnection alone does not prevent loss. Browser EventSource cannot set arbitrary authorization headers. SignalR is not a durable : plan reconnect behavior and a backplane or managed service.
#gRPC and choosing an API style
Controlled service-to-service communication may benefit from typed contracts and binary messages. generates client and server types from proto definitions and commonly carries Protobuf over /2.
syntax = "proto3";
option csharp_namespace = "Store.Contracts";
service Inventory {
rpc GetStock (StockRequest) returns (StockReply); // (1)
rpc WatchStock (StockRequest) returns (stream StockReply); // (2)
}
message StockRequest { string product_id = 1; }
message StockReply { int32 available = 1; }
- (1) Unary means one request and one response. Field numbers are part of the wire contract; reserve removed numbers instead of reusing them.
- (2) This is server streaming. Client and bidirectional streaming also exist; define deadlines, cancellation and flow control.
| Style | Good fit | Trade-off |
|---|---|---|
| Public resources and HTTP caching | Potential over-fetching or many requests | |
| GraphQL | Client-selected fields and graph queries | Cost limits, resolver and more complex authorization |
| gRPC | Typed internal and streaming | Limited native browser support; gRPC-Web or gateways and compatible tooling |
| SignalR | Live interfaces and groups | Connections and transient state need scale-out; not a complete replacement |
Do not choose gRPC on a blanket speed claim. Measure payloads and and verify proxy and client support. gRPC-Web does not expose every native streaming mode.
#API security checklist
A valid alone does not protect orders. Review every boundary before publishing an .
- Use and validate token issuer, audience, expiry and signature; keep secrets outside code.
- Enforce object and tenant authorization on every resource, beyond endpoint roles.
- Use separate request and allowed fields to prevent over-posting; validate input and bound bodies, uploads and page sizes.
- Parameterize values, allowlist sort columns, and protect outbound URLs against .
- Apply rate limits, quotas, timeouts, cancellation and query complexity limits; document 429 behavior.
- Restrict origins, recognizing that CORS is not authorization. Cookie-authenticated mutations need protection.
- Keep sensitive responses out of shared caches; review Cache-Control and cache keys.
- Use structured logs without tokens or payment details, IDs, change auditing and maintained dependencies.
References: HTTP semantics: RFC 9110 for method and status semantics, ProblemDetails: RFC 9457, and ASP.NET Core 10 for SSE and OpenAPI changes. Snippets are focused teaching excerpts; complete business models, authorization and persistence according to the contract.
Interview questions
No results
Check your reasoning
Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?
8.1What makes an API RESTful?Junior
I describe as an architectural style rather than a synonym for over . Its constraints are client-server separation, stateless requests, cacheability, a uniform interface, layering, and optional code on demand. I identify resources with stable URIs and use representations and standard HTTP semantics. Strict REST also includes hypermedia-driven transitions. In practice, I would be clear whether an follows all those constraints or is a pragmatic resource-oriented HTTP API.
8.2How do you design resource URLs?Junior
I use stable plural nouns such as orders and products, with identifiers for individual resources. Query parameters handle filtering, sorting and pagination. I nest paths only when ownership or context is useful, such as a customer's orders, and avoid deeply nested URLs. For an operation such as cancellation, I may create a cancellation resource or use a documented action. I design around the business contract rather than exposing database table structure directly.
8.3What is the difference between safe and idempotent methods?Junior
I use safe to mean that the requested operation does not change resource state, as with GET and HEAD. Logging can still happen as an incidental side effect. means repeating an identical request has the same intended server effect as sending it once. PUT and DELETE are idempotent but unsafe. Responses may differ between attempts, so a DELETE returning 204 and then 404 can still be idempotent.
8.4When do you choose POST, PUT or PATCH?Junior
I normally use POST to create a resource under a collection or initiate processing, especially when the server chooses its . PUT replaces the writable representation at a known URI and is . PATCH applies a partial modification according to a defined patch format. I do not assume PATCH is idempotent: setting a status repeatedly can be, but incrementing a quantity is not. For concurrent updates I also consider conditional requests using ETags.
8.5Which status codes matter when creating or processing an order?Junior
I return 201 when a new order is created and normally include its in Location. If processing is accepted but unfinished, I use 202 and provide a way to check progress. A successful operation without response content can return 204. Invalid input uses the documented 400 or 422 policy, a state conflict can use 409, and a failed If-Match precondition uses 412. I avoid hiding all outcomes behind 200.
8.6How do 401 and 403 differ?Junior
I return 401 when the request needs valid authentication credentials, for example because a bearer is missing or invalid, with the appropriate authentication challenge. I use 403 when authorization refuses the operation, usually after the caller has authenticated. For sensitive resources I may deliberately return 404 to avoid disclosing existence, following a consistent policy. In all cases I check access to the specific order, not just whether the caller has a valid token.
8.7How would you make a payment endpoint retry-safe?Mid
I require a stable key per logical payment and scope it to the caller and operation. A unique database constraint arbitrates concurrent requests, and I store a payload hash to reject key reuse with different input. Completed attempts replay the recorded response, while in-progress behavior and expiry are documented. Local changes commit atomically. For an external payment provider I also reuse its idempotency mechanism and reconcile uncertain outcomes rather than assuming my database covers the remote charge.
8.8When would you use cursor pagination instead of offset?Mid
I use offset for small administrative lists and direct access to numbered pages. For large feeds, cursor pagination avoids traversing a growing number of skipped rows. I use a deterministic sort, such as creation time plus a unique numeric identifier, and a matching index. The cursor captures both values and is tied to the filters. It reduces insert-related paging problems but does not create snapshot consistency when records themselves change, and arbitrary page jumps become harder.
8.9How do you expose filtering and sorting safely?Mid
I define a small, explicit vocabulary of filter fields, operators and sort directions instead of accepting arbitrary query expressions. I validate types, cap page sizes and apply authorization scope before filtering and paging. Values become parameters, while dynamic identifiers come from an allowlist. I also consider supporting indexes and query cost, because a syntactically valid filter can still be expensive. Stable ordering and documented cursor behavior make the endpoint predictable for clients.
8.10How do you version an API and identify breaking changes?Mid
I choose a consistent strategy, often a path segment for visibility, and document deprecation and retirement dates. Removing fields, changing types, adding required input or changing established behavior can break clients. Optional additions are usually compatible only if clients tolerate unknown fields and values. I use diffs and consumer contract tests to check that assumption. Before retiring a version I measure real usage and coordinate migration instead of relying only on a published date.
8.11What is contract-first development with OpenAPI?Mid
I write and review the contract before implementation, including schemas, status codes, authentication and error responses. Teams can then use mocks and generated clients while the service is being built. checks for incompatible changes, and behavioral tests verify guarantees the schema cannot express. In 10 I can use built-in OpenAPI generation to compare the implementation with the agreed document, but generating documentation alone does not make the process contract-first.
8.12How does spec-driven development differ from writing an API schema?Mid
I treat an as one part of a broader specification. The specification explains the user outcome, business rules, authorization, failure scenarios and acceptance criteria. I resolve ambiguity before planning implementation, then turn the criteria into reviewable tasks and tests. OpenAPI captures the boundary, while operational guarantees such as retry safety need additional detail. The value comes from shared, testable expectations, whether implementation is written by a person or assisted by an agent.
8.13Why use ProblemDetails rather than custom error strings?Mid
I use ProblemDetails to give clients a recognizable error structure rather than unrelated strings from each endpoint. The standard fields describe the problem type, title, status and occurrence, and I can add a stable application code and identifier. Clients should branch on stable semantics, not parse human-readable messages. I keep sensitive diagnostics in logs. provides registration and exception handling support, but I still define mappings for expected domain failures.
8.14What does AsyncAPI describe?Mid
I use AsyncAPI to document an event interface: channels, message schemas, send and receive operations, and relevant protocol bindings. It gives producers and consumers a shared contract, much as OpenAPI does for . I still document delivery behavior separately, including ordering scope, retries, duplicate handling and evolution. The document can support tooling and validation, but it does not implement a or guarantee exactly-once processing for business side effects.
8.15How do you secure and reliably process webhooks?Senior
I verify the provider's signature over the exact raw payload, validate its timestamp and compare signatures in constant time. I rotate secrets and deduplicate event identifiers because signatures do not stop replay. After durable inbox storage I acknowledge promptly and process asynchronously. The sender uses bounded retries with backoff and jitter, and we support dead-letter inspection and replay. I design state transitions for duplicate and out-of-order events and protect configurable callback destinations against .
8.16When would you choose SSE, WebSockets or SignalR?Mid
I choose for one-way server updates such as progress or order status, especially when ordinary streaming fits the infrastructure. WebSockets suit bidirectional, frequent messages but leave more protocol work to the application. SignalR gives me hubs, groups and transport negotiation on top of those concerns. I also plan authentication, proxy timeouts, reconnection, and scale-out. None of these choices alone provides durable message delivery or automatic recovery of every missed event.
8.17What are gRPC streaming modes and their limitations?Mid
I distinguish unary calls from server streaming, client streaming and bidirectional streaming. The proto contract generates typed , and streaming lets messages flow without creating a new request for each item. I set deadlines, propagate cancellation and account for slow consumers. Native is a good fit between controlled services, but browser clients usually need gRPC-Web or a gateway, and supported streaming modes differ. I verify the complete network path rather than assuming /2 support everywhere.
8.18How would you choose between REST, GraphQL, gRPC and SignalR?Senior
I start with client needs and interaction patterns. works well for public resources and caching. GraphQL helps clients request different graph-shaped views, but requires cost controls and careful resolver authorization. suits typed internal calls and streaming when clients and infrastructure support it. SignalR fits interactive push to connected users. I compare operational cost, compatibility and measured workloads, and may combine styles rather than force one mechanism to serve every boundary.
8.19What does HATEOAS add, and when is it worth using?Senior
I see as a way for representations to advertise available transitions through meaningful links. A client can discover that an order can currently be cancelled instead of hard-coding every transition . It still needs to understand relation and payload semantics, and the server must recheck authorization and state when the action runs. I use it when discoverability and long-lived client evolution justify the added representation design and tooling, rather than adding decorative links without client benefit.
8.20What would you check before publishing an order API?Senior
I check authentication, object and tenant authorization, boundaries, validation, parameterization and resource limits. I test that one customer cannot access another customer's order, even with its identifier. I review retry safety for payments, webhook verification, cache policies and error redaction. I also check rate limits, timeouts, telemetry and dependency maintenance. is only a browser access policy, and cookie-authenticated mutations need protection. Finally, I run compatibility tests against the published contract.
Test yourself
Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.
DELETE returns 204 first and 404 on retry. Is it necessarily non-idempotent?
What resolves two simultaneous requests using the same idempotency key?
Which response fits accepted but unfinished processing?
Which ordering is suitable for a creation-time cursor?
Adding an enum value is backward compatible when...
What does AddOpenApi plus MapOpenApi provide?
A signed webhook is received twice. What is still needed?
Which .NET 10 API writes an SSE result?
A stale If-Match value should normally produce...
A valid token lets customer A read customer B's order. What is missing?