An API (Application Programming Interface) is a contract that allows two software systems to communicate with each other. It defines:

  • How requests should be made
  • What data is expected
  • What responses will be returned

APIs abstract internal implementation and expose only what is necessary, enabling loose coupling between systems.

REST vs SOAP
Feature REST SOAP
Style Architectural style Strict protocol
Data format JSON (mostly) XML only
Transport HTTP mainly HTTP, SMTP, TCP
State Stateless Can be stateful
Performance Lightweight, fast Heavy, slow
Usage today Everywhere Mostly legacy systems

REST is preferred for modern applications due to simplicity and performance. SOAP is used in enterprise legacy systems.

An API is RESTful only if it adheres to REST constraints.

REST Architectural Constraints
1. Client Server Architecture
  • Client handles UI and user experience
  • Server handles data and business logic
  • Both evolve independently
2. Statelessness
  • Server does not store client session state
  • Each request is self contained
3. Cacheability
  • Responses specify cache rules
  • Improves performance and scalability
4. Uniform Interface

This is the most critical constraint.

Statelessness means that the server does not retain any information about the client's previous requests.

Each request must contain:
  • Authentication details
  • Required parameters
  • Contextual data
Benefits:
  • Easy horizontal scaling
  • Fault tolerance
  • Reduced server memory usage
Common Misconception: Using JWT does NOT make the server stateful. The state is stored on the client.
REST
  • Resource-oriented
  • URLs represent entities
  • HTTP semantics are meaningful

Example:

GET /orders/12
RPC (Remote Procedure Call)
  • Action-oriented
  • URLs represent operations
  • HTTP is used as a transport mechanism

Example:

POST /getOrder
REST
  • Multiple endpoints (one per resource)
  • Fixed response structure
  • Can lead to over fetching or under fetching
GraphQL
  • Single endpoint
  • Client specifies exactly what data it needs
  • Reduces over fetching
When to use REST:
  • Simple CRUD operations
  • Well-defined resources
  • Easier caching
When to use GraphQL:
  • Complex data requirements
  • Multiple related entities
  • Mobile apps (reduce bandwidth)

HTTP methods define what action the client wants to perform on a resource. Correct usage is critical for clarity, scalability, caching, security, and correctness.

Difference Between GET, POST, PUT, PATCH, DELETE

Each HTTP method has a clear semantic meaning. Misusing them is a design flaw, not a style choice.

Method Purpose Creates Resource Modifies Resource Idempotent Safe
GET Retrieve data No No Yes Yes
POST Create / Trigger Yes Yes No No
PUT Replace resource Yes (if absent) Yes (full) Yes No
PATCH Partial update No Yes (partial) Not guaranteed No
DELETE Remove resource No Yes Yes No
Idempotency

An operation is idempotent if performing it multiple times results in the same system state.

Example: PUT /users/10 Multiple identical PUT requests result in the same resource state.
HTTP Methods and Idempotency
Method Idempotent Explanation
GET Yes Read-only
PUT Yes Full replacement
DELETE Yes Deletes once
HEAD Yes Metadata only
POST No Creates new resource
PUT (Full Replacement)
  • Replaces the entire resource
  • Missing fields are removed
  • Idempotent by definition
PUT /profile/5 { "name": "John", "age": 30 } If address is omitted, it is deleted.
PATCH (Partial Update)
  • Updates only specified fields
  • Does not affect other fields
  • Not strictly idempotent
PATCH /profile/5 { "age": 31 } Only the provided attributes change.
Why Both Exist?
Reason Explanation
Clarity PUT means replace, PATCH means modify
Safety PATCH avoids accidental data loss
Efficiency PATCH sends less data
Semantics Different intentions, different guarantees

Technically yes. Practically no.

Explanation
  • HTTP specification does not forbid GET request bodies
  • However:
    • Servers often ignore them
    • Proxies may drop them
    • Caching systems do not consider them
Best Practice
  • Use query parameters for GET
  • Use POST if request data is complex
❌ Bad: GET /search (body contains filters) ✅ Good: GET /search?name=alice&age=25
Use POST When:
  1. Server generates the resource ID
  2. Operation is non-idempotent
  3. Action is not a full replacement
  4. Triggering processing (emails, payments)
POST /orders Server decides: /orders/8743
PUT is Used When:
  • Client knows the resource URI
  • Entire resource state is provided
  • Idempotency is required
PUT /orders/8743
Safe Methods

A method is safe if it does not modify server state.

Safe methods:

  • GET
  • HEAD
  • OPTIONS

Even if called repeatedly, they only read data.

Idempotent Methods

A method is idempotent if performing it multiple times results in the same system state.

Idempotent methods:

  • GET
  • PUT
  • DELETE
  • HEAD
Key Difference
Concept Meaning
Safe No side effects
Idempotent Same result on repetition

A method can be:

  • Idempotent but not safe (DELETE)
  • Safe and idempotent (GET)

HTTP status codes are three digit numbers returned by the server to indicate the result of a client's request. They are essential for API design because they communicate success, failure, and error context without requiring the client to parse the response body.

Status Code Categories
Range Category Meaning
1xxInformationalRequest received, processing continues
2xxSuccessRequest was successfully processed
3xxRedirectionFurther action needed from client
4xxClient ErrorProblem with the request itself
5xxServer ErrorServer failed to process a valid request
Success Codes (2xx)
CodeNameWhen to Use
200OKGET request succeeded, data returned
201CreatedPOST request succeeded, new resource created
202AcceptedRequest accepted for async processing (not yet completed)
204No ContentDELETE or PUT succeeded, nothing to return
Client Error Codes (4xx)
CodeNameWhen to Use
400Bad RequestInvalid syntax, missing required fields, validation failure
401UnauthorizedMissing or invalid authentication credentials
403ForbiddenAuthenticated but lacks permission for this resource
404Not FoundResource does not exist at the given URI
405Method Not AllowedHTTP method not supported for this endpoint
409ConflictDuplicate resource, version conflict, or state conflict
422Unprocessable EntityRequest is well formed but semantically invalid
429Too Many RequestsRate limit exceeded, client must slow down
Server Error Codes (5xx)
CodeNameWhen to Use
500Internal Server ErrorUnexpected server side failure
502Bad GatewayUpstream service returned an invalid response
503Service UnavailableServer is temporarily down (maintenance, overload)
504Gateway TimeoutUpstream service did not respond in time

This is one of the most commonly confused pairs in API design interviews.

401 Unauthorized
  • The client has not provided credentials or the credentials are invalid
  • The client should authenticate and retry
  • Think of it as: "Who are you? I don't know you."
403 Forbidden
  • The client is authenticated but does not have permission
  • Re-authenticating will not help
  • Think of it as: "I know who you are, but you can't do this."
// 401 example: no token provided GET /admin/dashboard Response: 401 Unauthorized // 403 example: regular user trying to access admin GET /admin/dashboard Authorization: Bearer valid_user_token Response: 403 Forbidden
400 Bad Request
  • The request is malformed at a syntactic level
  • Invalid JSON, missing Content-Type, bad query parameters
  • The server cannot even parse the request
422 Unprocessable Entity
  • The request is well formed but semantically invalid
  • Server understood the structure but the values are wrong
  • Example: email field present but value is not a valid email
// 400: malformed JSON POST /users Body: { "name": "John", // missing closing brace Response: 400 Bad Request // 422: valid JSON but bad data POST /users Body: { "name": "", "email": "not-an-email" } Response: 422 Unprocessable Entity { "errors": [ { "field": "name", "message": "Name is required" }, { "field": "email", "message": "Invalid email format" } ] }

Practical advice: Many APIs use 400 for both cases. If you want precision, use 422 for business logic validation. Either approach is acceptable as long as you are consistent.

Good URL design makes an API intuitive, predictable, and self documenting. Follow these rules consistently.

1. Use Nouns, Not Verbs
Good: GET /users Bad: GET /getUsers Good: POST /orders Bad: POST /createOrder Good: DELETE /users/5 Bad: POST /deleteUser?id=5
2. Use Plural Nouns for Collections
Good: /users, /orders, /products Bad: /user, /order, /product
3. Use Hierarchical Nesting for Relationships
GET /users/5/orders (orders belonging to user 5) GET /users/5/orders/12 (order 12 of user 5) GET /orders/12/items (items in order 12) Limit nesting to 2 levels maximum. Deeper than that, use query parameters or flat URLs.
4. Use Lowercase and Hyphens
Good: /order-items Bad: /orderItems Bad: /OrderItems Bad: /order_items
5. Use Query Parameters for Filtering
GET /products?category=electronics&sort=price&order=asc GET /users?status=active&role=admin&page=2

Not every action maps cleanly to a CRUD resource. Here are common patterns for non standard actions.

Option 1: Sub Resource
POST /orders/12/cancel POST /users/5/activate POST /payments/99/refund
Option 2: State as a Resource
PATCH /orders/12 { "status": "cancelled" } PUT /users/5/status { "active": true }
Option 3: Controller Resource

For complex processes that don't map to a single resource.

POST /search (complex search with body) POST /batch (batch processing) POST /export (trigger report generation)

Rule of thumb: Try sub resources first. If the action changes a state, use PATCH. Use controller resources only as a last resort.

HATEOAS (Hypermedia as the Engine of Application State) is a REST constraint where the API response includes links to related actions and resources. The client discovers available operations through these links rather than hardcoding URLs.

GET /orders/42 { "id": 42, "status": "shipped", "total": 99.99, "_links": { "self": { "href": "/orders/42" }, "cancel": { "href": "/orders/42/cancel", "method": "POST" }, "items": { "href": "/orders/42/items" }, "customer": { "href": "/customers/7" } } }
Benefits
  • API is self documenting and discoverable
  • Client does not need to hardcode URLs
  • Server can change URL structure without breaking clients
Reality

Most APIs do not implement HATEOAS in practice. It adds complexity and most teams prefer clear documentation over hypermedia links. However, it is frequently asked in interviews as a theoretical concept. Know what it is and when it could be useful, but be honest that most production APIs skip it.

1. API Key

A simple secret string passed in requests to identify the caller.

How it works:
  • Server generates a unique key for each client
  • Client includes key in every request (header or query param)
  • Server validates the key
GET /api/users Authorization: Api-Key sk_live_12345...
Pros:
  • Simple to implement
  • Good for server-to-server communication
  • Easy to revoke
Cons:
  • No user identity (only identifies the app)
  • Hard to implement fine-grained permissions
  • Risk if leaked
Use Case:

Machine-to-machine communication, backend services, third party API integrations.

2. OAuth 2.0

An authorization framework that allows third party apps to access user resources without exposing credentials.

How it works:
  • User authorizes app to access their data
  • Authorization server issues an access token
  • App uses token to make API requests
  • Token has limited scope and expiration
Flow Example (Authorization Code):
  1. App redirects user to authorization server
  2. User logs in and grants permission
  3. Server redirects back with authorization code
  4. App exchanges code for access token
  5. App uses token to access protected resources
Pros:
  • User doesn't share password with third party apps
  • Fine-grained permissions (scopes)
  • Tokens can be revoked without changing password
  • Supports refresh tokens for long-lived access
Cons:
  • Complex to implement
  • Requires multiple round trips
  • Token storage and management overhead
Use Case:

Third-party integrations (e.g., "Sign in with Google"), delegated access, social logins.

3. JWT (JSON Web Token)

A compact, self contained token format for securely transmitting information between parties.

Structure:
header.payload.signature Example: eyJhbGc... (header) .eyJzdWI... (payload - user data, expiry) .SflKxw... (signature - verification)
How it works:
  • Server creates JWT with user info and signs it
  • Client stores JWT (localStorage, cookie)
  • Client sends JWT with each request
  • Server verifies signature and extracts data
GET /api/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Pros:
  • Stateless (no server-side storage needed)
  • Self-contained (includes user info)
  • Works across multiple servers
  • Easy to scale
Cons:
  • Cannot be revoked before expiration
  • Larger than session IDs
  • Sensitive data in payload is visible (base64 encoded, not encrypted)
Use Case:

Single Sign-On (SSO), microservices authentication, mobile apps, stateless APIs.

Comparison Table
Feature API Key OAuth JWT
Complexity Simple Complex Moderate
User Identity No Yes Yes
Stateless No No Yes
Revocation Easy Easy Hard
Best For Server-to-server Third-party access Stateless APIs

Tokens should be sent in the Authorization header using the Bearer scheme.

✅ Recommended:
GET /api/users Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Why Authorization Header?
  • Secure: Not logged in URLs or browser history
  • Standard: Industry convention (RFC 6750)
  • Clean: Separates auth from business logic
  • CORS-friendly: Works with cross origin requests
❌ Avoid:
1. Query Parameters
GET /api/users?token=abc123 // BAD

Problems:

  • Logged in server logs
  • Visible in browser history
  • Can be leaked via Referer header
2. Request Body (for GET requests)
GET /api/users { "token": "abc123" } // BAD

Problems:

  • GET requests shouldn't have a body
  • Breaks HTTP semantics
  • Caching issues
3. Cookies (for APIs)

Cookies work for browser based sessions but are problematic for APIs:

  • CSRF vulnerability
  • Not ideal for mobile/desktop apps
  • Same-origin limitations
Exception: Web Applications

For traditional web apps, HttpOnly cookies are acceptable and can be more secure:

  • Protected from XSS attacks
  • Automatically sent by browser
  • Require CSRF protection
Access Token

A short-lived token used to access protected resources.

Characteristics:
  • Short lifespan: 15 minutes to 1 hour
  • Sent with every request
  • Contains user permissions/claims
  • Cannot be easily revoked (before expiry)
Refresh Token

A long-lived token used to obtain new access tokens without re authentication.

Characteristics:
  • Long lifespan: Days to months
  • Used only to get new access tokens
  • Stored securely (database)
  • Can be revoked
Flow:
  1. User logs in → Server returns access token + refresh token
  2. Client uses access token for API requests
  3. Access token expires
  4. Client sends refresh token to get new access token
  5. Server validates refresh token and issues new access token
// Login Response { "access_token": "eyJhbGc...", // expires in 1 hour "refresh_token": "dGhpcyB...", // expires in 30 days "token_type": "Bearer", "expires_in": 3600 } // Refresh Request POST /auth/refresh { "refresh_token": "dGhpcyB..." } // New Access Token { "access_token": "newToken...", "expires_in": 3600 }
Why Use Both?
Benefit Explanation
Security Short-lived access tokens limit damage if leaked
User Experience Users don't need to re login frequently
Revocation Refresh tokens can be revoked from database
Rotation Can implement refresh token rotation for added security
Best Practices:
  • Store refresh tokens securely (encrypted in DB)
  • Implement refresh token rotation
  • Set appropriate expiration times
  • Revoke refresh tokens on logout
  • Detect and handle refresh token reuse (security breach)
1. Authentication & Authorization
  • Use OAuth 2.0 or JWT for authentication
  • Implement role-based access control (RBAC)
  • Validate permissions on every request
2. HTTPS Only
  • Always use TLS/SSL encryption
  • Reject HTTP requests
  • Use HSTS headers
3. Input Validation
  • Validate all input data
  • Sanitize user inputs
  • Use parameterized queries (prevent SQL injection)
  • Limit request size
4. Rate Limiting
  • Prevent brute force attacks
  • Protect against DDoS
  • Use token bucket or sliding window algorithms
HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1640995200
5. API Keys & Secrets
  • Never hardcode secrets in code
  • Use environment variables
  • Rotate keys regularly
  • Store in secure vaults (AWS Secrets Manager, HashiCorp Vault)
6. CORS Configuration
  • Whitelist specific origins
  • Don't use wildcard (*) in production
  • Validate Origin header
7. Security Headers
  • Content-Security-Policy
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Strict-Transport-Security
8. Logging & Monitoring
  • Log all authentication attempts
  • Monitor for suspicious patterns
  • Set up alerts for anomalies
  • Never log sensitive data (passwords, tokens)
9. API Versioning
  • Version your APIs
  • Maintain backward compatibility
  • Deprecate old versions gracefully
10. Error Handling
  • Don't expose sensitive info in errors
  • Use generic error messages
  • Log detailed errors server-side

A replay attack occurs when an attacker intercepts a valid request and resends it to gain unauthorized access or repeat an action.

Prevention Strategies:
1. Timestamps

Include a timestamp in each request and reject requests older than a threshold (e.g., 5 minutes).

GET /api/payment Authorization: Bearer token... X-Timestamp: 1640995200 Server checks: if (currentTime - requestTimestamp > 300 seconds) { return 401 Unauthorized; }
2. Nonce (Number Used Once)

A unique value for each request. Server tracks used nonces and rejects duplicates.

GET /api/payment Authorization: Bearer token... X-Nonce: 8f3b4e2a-9c1d-4a5b-8e6f-7d9c3a2b1e4f X-Timestamp: 1640995200 Server logic: if (nonceExists(nonce)) { return 401 Unauthorized; // Replay detected! } storeNonce(nonce, expiryTime);
3. Request Signing

Create a signature using request data + timestamp + secret key. Server verifies signature.

// Client Side const signature = HMAC_SHA256( method + url + timestamp + body, secretKey ); // Request POST /api/payment X-Signature: abc123... X-Timestamp: 1640995200 // Server verifies expectedSignature = HMAC_SHA256( method + url + timestamp + body, secretKey ); if (signature !== expectedSignature) { return 401 Unauthorized; }
4. Token Expiration
  • Use short-lived tokens (15-60 minutes)
  • Implement refresh token mechanism
  • Expired tokens cannot be replayed
5. HTTPS Only
  • Prevents interception of requests
  • TLS encryption protects data in transit
  • Essential for all other strategies to work
6. Idempotency Keys

For critical operations (payments, orders), use idempotency keys to ensure operations execute only once.

POST /api/payments Idempotency-Key: unique-key-123
Best Practice: Combine Multiple Strategies
  • HTTPS + Timestamp + Nonce + Short-lived tokens
  • Request signing for critical operations
  • Idempotency keys for financial transactions
Example: AWS API Request Signing

AWS uses a combination of:

  • Timestamp (X-Amz-Date header)
  • Request signing (Authorization header with signature)
  • Short-lived credentials
1. URL Path Versioning (Most Common)
GET /api/v1/users GET /api/v2/users
  • Most explicit and visible
  • Easy to test in browser
  • Clear which version the client is using
  • Used by: Stripe, GitHub, Google
2. Query Parameter Versioning
GET /api/users?version=1 GET /api/users?version=2
  • Keeps URL path clean
  • Easy to add as optional parameter
  • Can default to latest version if omitted
3. Header Versioning
GET /api/users Accept: application/vnd.myapi.v2+json // or custom header GET /api/users X-API-Version: 2
  • Cleanest URLs
  • Follows HTTP content negotiation standards
  • Harder to test in browser
  • Used by: GitHub (also supports URL versioning)
Comparison
ApproachVisibilityEase of UseCaching
URL PathHighVery easyExcellent
Query ParamMediumEasyGood
HeaderLowHarderRequires Vary header

Recommendation: Use URL path versioning for public APIs. It is the industry standard and most developer friendly. Whatever approach you choose, version from day one and plan for backward compatibility.

Backward Compatible Changes (Safe)
  • Adding new optional fields to responses
  • Adding new endpoints
  • Adding new optional query parameters
  • Adding new enum values (if client handles unknown values)
Breaking Changes (Require New Version)
  • Removing or renaming fields
  • Changing field types (string to integer)
  • Changing URL structure
  • Changing authentication mechanism
  • Changing error response format
Deprecation Strategy
  1. Announce deprecation with timeline (6 to 12 months)
  2. Add Sunset and Deprecation headers to responses
  3. Monitor usage of deprecated versions
  4. Communicate directly with high volume consumers
  5. Remove old version only after migration is complete
// Deprecation headers HTTP/1.1 200 OK Sunset: Sat, 01 Nov 2026 00:00:00 GMT Deprecation: true Link: </api/v2/users>; rel="successor-version"

Rate limiting restricts the number of API requests a client can make within a given time window. It is essential for protecting APIs from abuse, ensuring fair usage, preventing DDoS attacks, and maintaining service stability.

Common Rate Limiting Algorithms
1. Token Bucket
  • A bucket holds tokens that refill at a fixed rate
  • Each request consumes one token
  • Allows burst traffic (up to bucket size)
  • Used by: AWS, Stripe
2. Sliding Window
  • Counts requests in a rolling time window
  • Smoother distribution than fixed window
  • No burst spikes at window boundaries
3. Fixed Window
  • Simple counter reset at fixed intervals
  • Can allow double the limit at window boundaries
  • Easiest to implement
Rate Limit Response Headers
HTTP/1.1 200 OK X-RateLimit-Limit: 1000 (max requests per window) X-RateLimit-Remaining: 847 (requests left in current window) X-RateLimit-Reset: 1640995200 (unix timestamp when window resets) // When limit is exceeded: HTTP/1.1 429 Too Many Requests Retry-After: 60 (seconds to wait before retrying) { "error": "Rate limit exceeded", "retry_after": 60 }

Most production APIs implement tiered rate limits based on the client's subscription plan.

PlanRequests/MinRequests/DayBurst Limit
Free601,00010
Pro30050,00050
Enterprise3,000Unlimited500
Implementation Tips
  • Rate limit by API key, user ID, or IP address
  • Use Redis or in memory stores for fast counter lookups
  • Apply different limits to different endpoints (write endpoints stricter than reads)
  • Always return rate limit headers so clients can self throttle
  • Provide a rate limit status endpoint for dashboard integration

HTTP caching avoids redundant data transfers by storing responses and reusing them for identical requests. Proper caching can reduce server load by 50 to 90 percent and dramatically improve response times.

Cache-Control Header
// Cache for 1 hour, allow shared caches (CDNs) Cache-Control: public, max-age=3600 // Cache for 5 minutes, private (user specific data) Cache-Control: private, max-age=300 // Never cache (sensitive data) Cache-Control: no-store // Cache but always revalidate before using Cache-Control: no-cache
ETag (Entity Tag) for Conditional Requests
// First request GET /products/42 Response: ETag: "abc123" Cache-Control: max-age=300 // After cache expires, client validates: GET /products/42 If-None-Match: "abc123" // If unchanged: 304 Not Modified (no body, saves bandwidth) // If changed: 200 OK (full response with new ETag)
Last-Modified for Time Based Validation
// First request GET /reports/monthly Response: Last-Modified: Wed, 15 Apr 2026 10:00:00 GMT // Subsequent request GET /reports/monthly If-Modified-Since: Wed, 15 Apr 2026 10:00:00 GMT // If unchanged: 304 Not Modified // If changed: 200 OK with new data
1. CDN Caching (Edge Caching)
  • Cache responses at CDN edge locations worldwide
  • Best for: static content, public API responses, rarely changing data
  • Example: Cloudflare, AWS CloudFront, Fastly
2. Application Level Cache
  • Store computed results in Redis or Memcached
  • Best for: expensive database queries, aggregations, user sessions
  • Use cache invalidation strategies (TTL, event based, write through)
3. Database Query Cache
  • Cache frequently executed queries at the database level
  • Best for: read heavy workloads with predictable query patterns
Cache Invalidation Patterns
PatternHow It WorksTrade off
TTL (Time to Live)Cache expires after set durationSimple but may serve stale data
Write ThroughUpdate cache on every writeAlways fresh but slower writes
Write BehindUpdate cache now, DB later (async)Fast writes but risk of data loss
Cache AsideApp checks cache first, loads from DB on missMost flexible, most commonly used

A well designed error response gives the client enough information to understand what went wrong, why it happened, and how to fix it. Never return generic messages that leave the client guessing.

Standard Error Response Structure
{ "error": { "code": "VALIDATION_ERROR", "message": "One or more fields failed validation", "details": [ { "field": "email", "message": "Must be a valid email address", "rejected_value": "not-an-email" }, { "field": "age", "message": "Must be between 1 and 150", "rejected_value": -5 } ], "request_id": "req_abc123", "documentation_url": "https://api.example.com/docs/errors#VALIDATION_ERROR" } }
Key Components
  • code: Machine readable error type for programmatic handling
  • message: Human readable description
  • details: Array of specific field level errors
  • request_id: Unique ID for debugging and support tickets
  • documentation_url: Link to relevant error documentation
Common Mistakes
  • Returning 200 OK with an error in the body
  • Exposing stack traces or internal paths in production
  • Using generic "Something went wrong" for all errors
  • Inconsistent error formats across endpoints
Single Resource Response
GET /users/42 { "data": { "id": 42, "name": "Alice", "email": "alice@example.com", "created_at": "2026-03-15T10:30:00Z" } }
Collection Response with Pagination
GET /users?page=2&limit=10 { "data": [ { "id": 41, "name": "Alice" }, { "id": 42, "name": "Bob" } ], "pagination": { "page": 2, "limit": 10, "total": 156, "total_pages": 16 } }
Best Practices
  • Wrap responses in a data key for consistency
  • Use ISO 8601 format for all timestamps
  • Use snake_case for JSON field names (most common convention)
  • Always include pagination metadata for collections
  • Return the created or updated resource in POST and PUT responses
Polling

The client repeatedly requests the server at regular intervals to check for updates.

// Client polls every 30 seconds while (true) { response = GET /orders/42/status if (response.status == "completed") break; sleep(30 seconds); }
  • Simple to implement
  • Wasteful (most requests return no new data)
  • Delayed detection (depends on poll interval)
  • Increases server load with scale
Webhooks (Push Notifications)

The server sends an HTTP POST to a client specified URL when an event occurs.

// Server pushes event to client's URL POST https://myapp.com/webhooks/orders { "event": "order.completed", "data": { "order_id": 42, "status": "completed", "completed_at": "2026-04-21T14:30:00Z" }, "webhook_id": "wh_abc123", "timestamp": "2026-04-21T14:30:01Z" }
  • Real time notifications
  • Efficient (no wasted requests)
  • Reduces server load
  • More complex to implement and secure
Comparison
FeaturePollingWebhooks
DirectionClient pulls from serverServer pushes to client
LatencyDepends on poll intervalNear real time
EfficiencyWastefulEfficient
ComplexitySimpleMore complex
ReliabilityClient controls retryNeeds retry logic on server
1. Signature Verification

Sign each webhook payload with a shared secret so the receiver can verify it came from you.

// Server side: create signature signature = HMAC_SHA256(payload, webhook_secret) // Send with header POST https://client.com/webhooks X-Webhook-Signature: sha256=abc123... // Client side: verify expected = HMAC_SHA256(request.body, shared_secret) if (signature != expected) reject request
2. Replay Protection
  • Include timestamp in webhook payload
  • Reject webhooks older than 5 minutes
  • Track delivered webhook IDs to prevent duplicates
3. Retry Strategy
  • Retry failed deliveries with exponential backoff
  • Example: retry at 1m, 5m, 30m, 2h, 24h
  • Alert the user after maximum retries are exhausted
  • Provide a manual retry button in the dashboard
4. HTTPS Only
  • Only deliver webhooks to HTTPS endpoints
  • Validate SSL certificates
  • Reject self signed certificates in production

Used by: Stripe, GitHub, Shopify, Twilio all use HMAC signature verification for webhook security. This is the industry standard approach.

A payment system API must prioritize security, correctness, idempotency, and reliability. Performance is important, but correctness beats speed every time when money is involved.

Core Requirements
  • Secure authentication
  • Idempotent payment requests
  • Strong validation
  • Auditability
  • Failure and retry handling
Key Resources
  • customers
  • payments
  • transactions
  • refunds
API Endpoints
Create a Payment
POST /payments Headers: Authorization: Bearer Idempotency-Key: Request Body: { "customer_id": "cust_123", "amount": 5000, "currency": "INR", "payment_method": "card" } Response: { "payment_id": "pay_456", "status": "processing" }
Payment Status
GET /payments/{payment_id}
Refund Payment
POST /payments/{payment_id}/refund
Critical Design Considerations
Idempotency
  • Prevents duplicate charges on retries
  • Mandatory for POST /payments
Asynchronous Processing
  • Payment gateways are slow and unreliable
  • Use webhooks for final confirmation
Security
  • HTTPS only
  • Token-based auth
  • Never expose internal transaction IDs

A social media feed must optimize for read performance, not writes. Writes are occasional. Reads are constant.

Core Requirements
  • Pagination
  • Sorting by time or relevance
  • Scalability
  • Caching
Key Resources
  • users
  • posts
  • feeds
  • likes
  • comments
API Endpoints
Create Post
POST /posts
Get User Feed
GET /feed?cursor=abc123&limit=20 Response: { "items": [...], "next_cursor": "def456" }
Pagination Strategy

Cursor-based pagination is preferred over offset-based.

Why:

  • Stable results
  • Better performance
  • No duplicate or missing posts
Feed Generation Approaches
Fan-out on Write
  • Push post IDs to followers
  • Faster reads
  • Higher write cost
Fan-out on Read
  • Generate feed dynamically
  • Slower reads
  • Lower storage cost

File upload APIs must handle large payloads, network failures, and resumability.

Simple Upload (Small Files)
POST /files Content-Type: multipart/form-data
Large File Upload
Step 1: Initiate Upload
POST /uploads Response: { "upload_id": "upl_123" }
Step 2: Upload Chunks
PUT /uploads/{upload_id}/parts/{part_number}
Step 3: Complete Upload
POST /uploads/{upload_id}/complete
Design Considerations
  • Chunking
  • Retry failed chunks
  • Virus scanning
  • Size and type validation

Microservices APIs must be independent, loosely coupled, and resilient.

Core Principles
Service Ownership
  • Each service owns its data
  • No shared databases
API Contracts
  • Backward compatible
  • Versioned
Communication Patterns
  • REST for synchronous calls
  • Events for asynchronous communication
API Gateway Pattern
  • Authentication
  • Rate limiting
  • Routing
  • Aggregation
Failure Handling
  • Timeouts
  • Retries with backoff
  • Circuit breakers
Observability
  • Centralized logging
  • Metrics
  • Distributed tracing
1. Security First
  • Always use HTTPS
  • Implement proper authentication and authorization
  • Validate all inputs
  • Never expose sensitive internal IDs
2. Design for Reliability
  • Make operations idempotent where possible
  • Handle failures gracefully
  • Use appropriate status codes
  • Implement proper error responses
3. Optimize for Performance
  • Use caching strategically
  • Implement pagination for large datasets
  • Consider async operations for slow processes
  • Use appropriate HTTP methods
4. Plan for Scale
  • Version your APIs from the start
  • Design for backward compatibility
  • Implement rate limiting
  • Use API gateways for routing and aggregation
5. Maintain Observability
  • Log all requests and responses
  • Track metrics and performance
  • Implement distributed tracing
  • Monitor error rates and latency