Table of Contents

    REST

    API DESIGN & SERVICE CONTRACTS

    REST

    Learn how to design resource-oriented APIs using HTTP methods, representations, status codes, stateless requests, caching, validation, idempotency and consistent service contracts.

    Introduction

    An Application Programming Interface, commonly abbreviated as API, defines how software components communicate. A well-designed API provides a stable contract that allows clients and services to evolve independently.

    REST stands for Representational State Transfer. REST is an architectural style for distributed systems. A REST-oriented API models important concepts as resources, identifies those resources through URIs and uses standard HTTP semantics to operate on their representations.

    REST-oriented API design commonly involves:

    • Resource identification
    • Representations
    • Standard HTTP methods
    • HTTP status codes
    • Stateless requests
    • Cache controls
    • Uniform interaction patterns
    • Validation and error contracts
    • Authentication and authorization
    • Idempotency and retry behaviour

    Core idea: A REST API should expose meaningful resources and use HTTP semantics consistently. Clients should understand an operation from its method, resource URI, fields, representation and documented response contract.

    In your System Design curriculum, REST is Topic 4.1 under API Design & Service Contracts. This module also covers RPC and gRPC, GraphQL, resource modelling, pagination, versioning, idempotency, authentication versus authorization and rate-limit semantics.

    Prerequisites

    # Prerequisite Why It Is Needed
    1 HTTP methods and status codes REST APIs use HTTP semantics to express operations and outcomes.
    2 HTTP fields and representations Clients and servers exchange metadata and resource representations.
    3 JSON JSON is commonly used to represent API resources and errors.
    4 TLS and HTTPS API credentials and application data require protected transport.
    5 Authentication and authorization basics Identifying a caller and permitting an operation are separate responsibilities.
    6 Database fundamentals Many API resources are backed by persistent application state.

    What Is an API Contract?

    An API contract defines the observable behaviour on which clients can rely.

    A complete contract can define:

    • Resource URIs
    • Supported methods
    • Request fields
    • Request body schemas
    • Response schemas
    • Status codes
    • Error formats
    • Authentication requirements
    • Authorization rules
    • Validation constraints
    • Pagination behaviour
    • Retry and idempotency behaviour
    • Rate-limit behaviour
    • Compatibility guarantees
    Service Contract
    request → validation → authorization → resource operation → documented response

    Resource-oriented Design

    A resource is a concept or entity exposed through the API. Examples include customers, orders, products, invoices and shipments.

    Resource-oriented URIs commonly use nouns:

    /customers
    /customers/42
    /customers/42/orders
    /orders/9001
    /products
    /products/ABC-100
    Operation-oriented URI
    POST /createCustomer
    GET /getCustomerById?id=42
    POST /deleteCustomer?id=42
    Resource-oriented API
    POST /customers
    GET /customers/42
    DELETE /customers/42

    The HTTP method expresses the intended operation, while the URI identifies the target resource.

    Collection and Item Resources

    Resource Example URI Purpose
    Customer collection /customers Represents the customer collection
    Customer item /customers/42 Represents one customer
    Order collection /orders Represents the order collection
    Order item /orders/9001 Represents one order
    Customer orders /customers/42/orders Represents orders associated with one customer

    HTTP Methods in REST

    Method Common Resource Operation Safe Idempotent
    GET Retrieve a representation Yes Yes
    HEAD Retrieve response metadata without response content Yes Yes
    POST Submit data for processing or create a subordinate resource No Not inherently
    PUT Create or replace the state of the target resource No Yes
    PATCH Apply a partial modification No Depends on the patch operation
    DELETE Remove the target resource association No Yes
    OPTIONS Describe communication options Yes Yes

    GET

    GET requests retrieve a representation of a resource. A GET request should not request a state-changing operation.

    GET /customers/42 HTTP/1.1
    Host: api.example.com
    Accept: application/json
    Authorization: Bearer ACCESS_TOKEN
    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: private, max-age=60
    ETag: "customer-42-v7"
    
    {
        "id": 42,
        "name": "Example Customer",
        "status": "active"
    }

    Avoid State Changes through GET

    Unsafe resource design
    GET /orders/9001/cancel
    Explicit state-changing operation
    POST /orders/9001/cancellation

    Browsers, caches, crawlers and monitoring systems can issue GET requests without expecting business state to change.

    POST

    POST submits content for processing according to the target resource's semantics. It is commonly used to create a resource within a collection.

    POST /customers HTTP/1.1
    Host: api.example.com
    Content-Type: application/json
    Accept: application/json
    Idempotency-Key: client-generated-key
    
    {
        "name": "Example Customer",
        "email": "customer@example.com"
    }
    HTTP/1.1 201 Created
    Location: /customers/42
    Content-Type: application/json
    
    {
        "id": 42,
        "name": "Example Customer",
        "email": "customer@example.com",
        "status": "active"
    }

    A successful creation response can use 201 Created and identify the created resource through the Location response field.

    PUT

    PUT creates or replaces the state of the target resource using the supplied representation.

    PUT /customers/42 HTTP/1.1
    Host: api.example.com
    Content-Type: application/json
    If-Match: "customer-42-v7"
    
    {
        "name": "Updated Customer",
        "email": "updated@example.com",
        "status": "active"
    }

    The contract should explicitly define whether omitted fields are removed, reset or rejected.

    PATCH

    PATCH applies a partial modification according to the selected patch media type and API contract.

    PATCH /customers/42 HTTP/1.1
    Host: api.example.com
    Content-Type: application/merge-patch+json
    If-Match: "customer-42-v7"
    
    {
        "status": "inactive"
    }

    A patch should clearly distinguish between:

    • A field that is not supplied
    • A field explicitly set to null
    • A field being removed
    • A field retaining its current value

    DELETE

    DELETE /customers/42 HTTP/1.1
    Host: api.example.com
    Authorization: Bearer ACCESS_TOKEN
    HTTP/1.1 204 No Content

    DELETE expresses removal of the target resource association. The application can implement physical deletion, logical deletion, archival or another documented lifecycle transition.

    Contract rule: A client should not need to guess whether DELETE performs immediate physical deletion, soft deletion or an asynchronous deletion workflow. Document the observable behaviour.

    Safe and Idempotent Methods

    A safe method is intended for information retrieval and should not request a change to application state.

    An idempotent method has the same intended effect when an identical request is applied once or several times.

    PUT /customers/42
    
    Apply once:
    Customer state becomes version X.
    
    Apply the same replacement again:
    Customer state remains version X.
    
    
    POST /orders
    
    Apply once:
    Order 100 is created.
    
    Apply again without idempotency protection:
    Order 101 might also be created.

    Idempotency describes the intended effect, not whether every response, timestamp, log entry or generated metadata is identical.

    Resource Representations

    A resource is a conceptual object. A representation is the transferred form describing some state of that resource.

    {
        "id": 9001,
        "status": "confirmed",
        "currency": "USD",
        "total": 149.50,
        "customer": {
            "id": 42,
            "name": "Example Customer"
        },
        "links": {
            "self": "/orders/9001",
            "customer": "/customers/42"
        }
    }

    A representation should use stable field names, documented data types, defined nullability and consistent formats.

    Content Types

    The Content-Type field describes the media type of request or response content.

    Content-Type: application/json

    The Accept request field communicates media types acceptable to the client.

    Accept: application/json

    An API should reject unsupported request content types with a documented result rather than attempting to guess the payload format.

    REST Status Codes

    Status Typical Meaning in an API
    200 OK The operation succeeded and a response representation is returned
    201 Created A new resource was created
    202 Accepted The request was accepted for asynchronous processing
    204 No Content The operation succeeded without response content
    304 Not Modified A conditional retrieval can reuse a cached representation
    400 Bad Request The request is malformed or fails general request requirements
    401 Unauthorized Authentication credentials are missing or unacceptable
    403 Forbidden The request is understood but not permitted
    404 Not Found The target resource was not found or is intentionally not disclosed
    405 Method Not Allowed The target resource does not support the method
    409 Conflict The request conflicts with current resource state
    412 Precondition Failed A request precondition such as If-Match was false
    415 Unsupported Media Type The request content type is unsupported
    422 Unprocessable Content The content syntax is understood but semantic validation fails
    429 Too Many Requests The caller exceeded an applicable rate policy
    500 Internal Server Error An unexpected server failure occurred
    503 Service Unavailable The service is temporarily unable to process the request

    Error Response Contract

    APIs should use a consistent machine-readable error representation.

    {
        "type": "https://api.example.com/problems/validation-error",
        "title": "Request validation failed",
        "status": 422,
        "detail": "One or more request fields are invalid.",
        "instance": "/customers/requests/req-8f21",
        "errors": [
            {
                "field": "email",
                "code": "invalid_format",
                "message": "Enter a valid email address."
            }
        ],
        "traceId": "8f21c9a7"
    }

    A useful error contract can include:

    • A stable machine-readable error type or code
    • A human-readable title
    • The HTTP status
    • A safe explanation
    • Field-level validation errors
    • A correlation or trace identifier
    Information disclosure
    {
        "error": "SQLSTATE[42S02\]: Table 'prod.users' not found",
        "query": "SELECT * FROM users WHERE email = ...",
        "file": "/var/www/app/UserRepository.php",
        "line": 87
    }
    Safe external error
    {
        "type": "internal-error",
        "title": "The request could not be completed",
        "status": 500,
        "traceId": "8f21c9a7"
    }

    Detailed diagnostic information belongs in protected server logs, linked through an appropriate correlation identifier.

    Request Validation

    Validation should occur before applying a business operation.

    Validation Flow
    parse request → validate media type → validate schema → validate business rules → execute operation

    Validation can include:

    • Required fields
    • Data types
    • String length
    • Numeric range
    • Enumeration values
    • Date and time formats
    • Identifier format
    • Cross-field relationships
    • Current business state
    • Maximum request size

    Authentication vs Authorization

    Concern Question
    Authentication Who or what is calling the API?
    Authorization Is the authenticated caller allowed to perform this operation on this resource?
    Request arrives
          |
          v
    Validate credential
          |
          v
    Establish caller identity
          |
          v
    Load target resource
          |
          v
    Evaluate operation-level authorization
          |
          +--> Allowed: continue
          |
          +--> Denied: reject

    Authentication success does not grant access to every resource. Every protected operation requires an authorization decision.

    Prevent Object-level Authorization Defects

    Trusting the requested identifier
    GET /accounts/9002
    The API returns account 9002
    because the caller is authenticated.
    Resource-level authorization
    1. Authenticate the caller.
    2. Load account 9002.
    3. Evaluate whether the caller may view account 9002.
    4. Return the representation only when authorized.

    Stateless Requests

    REST's stateless constraint requires each request to contain the information necessary for the server to understand and process it.

    GET /orders/9001 HTTP/1.1
    Host: api.example.com
    Authorization: Bearer ACCESS_TOKEN
    Accept: application/json
    X-Correlation-ID: request-1042

    Statelessness does not mean the application has no persistent state. Orders, users and authorization data can exist in databases. It means the server does not depend on undocumented conversational context from an earlier request.

    REST and Caching

    HTTP caching can reduce latency, server load and repeated data transfer.

    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: private, max-age=60
    ETag: "customer-42-v7"

    A cache policy should define:

    • Whether the response can be cached
    • Whether a shared cache can store it
    • How long the response remains fresh
    • How stale responses are revalidated
    • Which request fields affect the representation
    • Whether authenticated or personal data can be cached

    Conditional Requests with ETags

    Conditional GET

    GET /customers/42 HTTP/1.1
    Host: api.example.com
    If-None-Match: "customer-42-v7"
    HTTP/1.1 304 Not Modified
    ETag: "customer-42-v7"

    The client can reuse its cached representation when the validator still matches.

    Optimistic Update

    PATCH /customers/42 HTTP/1.1
    Host: api.example.com
    Content-Type: application/merge-patch+json
    If-Match: "customer-42-v7"
    
    {
        "status": "inactive"
    }

    If the resource changed after the client retrieved version 7, the server can reject the update because the precondition is false.

    Idempotency Keys

    POST is not inherently idempotent. A client can use an idempotency key when safely retrying a supported operation after an uncertain response.

    POST /payments HTTP/1.1
    Host: api.example.com
    Content-Type: application/json
    Idempotency-Key: b610fb48-a5ac-4d54-93d2-a1f6e5cc6844
    
    {
        "orderId": 9001,
        "amount": 149.50,
        "currency": "USD"
    }
    First request:
    
    Validate idempotency key
    Process payment
    Store operation result
    Return response
    
    
    Retry with same key and same request:
    
    Find stored result
    Do not create another payment
    Return the original outcome
    
    
    Same key with different request:
    
    Reject as an idempotency-key conflict

    Synchronous vs Asynchronous Operations

    A short operation can return its final result synchronously. A long-running operation can return an operation resource.

    POST /reports HTTP/1.1
    Host: api.example.com
    Content-Type: application/json
    
    {
        "reportType": "annual-summary",
        "year": 2026
    }
    HTTP/1.1 202 Accepted
    Location: /operations/op-701
    Content-Type: application/json
    Retry-After: 5
    
    {
        "operationId": "op-701",
        "status": "pending"
    }

    Operation Status

    GET /operations/op-701 HTTP/1.1
    Host: api.example.com
    Accept: application/json
    {
        "operationId": "op-701",
        "status": "completed",
        "result": {
            "reportId": "report-2026-17",
            "href": "/reports/report-2026-17"
        }
    }

    Collection Filtering and Sorting

    GET /orders?status=confirmed&sort=-createdAt HTTP/1.1
    Host: api.example.com
    Accept: application/json

    The contract should define:

    • Supported filter fields
    • Supported operators
    • Ordering syntax
    • Default ordering
    • Null handling
    • Case sensitivity
    • Maximum complexity
    • Unsupported-filter behaviour

    Pagination

    Collection endpoints should avoid returning an unlimited number of resources.

    GET /orders?limit=25&after=opaque-cursor HTTP/1.1
    Host: api.example.com
    Accept: application/json
    {
        "items": [
            {
                "id": 9001,
                "status": "confirmed"
            },
            {
                "id": 9002,
                "status": "processing"
            }
        ],
        "page": {
            "limit": 25,
            "nextCursor": "opaque-next-cursor",
            "hasMore": true
        }
    }

    Cursor pagination can remain stable under concurrent inserts when the cursor and ordering contract are designed correctly.

    API Versioning

    Versioning provides a controlled way to introduce incompatible contract changes.

    Common approaches include:

    • Path versioning
    • Media-type or representation versioning
    • Field-based evolution without an immediate new major version
    Path example:
    
    /v1/customers/42
    /v2/customers/42

    Versioning should not replace compatibility discipline. Prefer additive changes when clients can safely ignore new optional fields.

    Hypermedia Links

    A representation can include links to related resources and supported transitions.

    {
        "id": 9001,
        "status": "confirmed",
        "links": {
            "self": {
                "href": "/orders/9001"
            },
            "customer": {
                "href": "/customers/42"
            },
            "cancellation": {
                "href": "/orders/9001/cancellation",
                "method": "POST"
            }
        }
    }

    Hypermedia can reduce the need for clients to construct every related URI independently. Links must still follow documented authorization and lifecycle rules.

    Rate-limit Semantics

    Rate limiting protects service capacity and promotes fair usage.

    HTTP/1.1 429 Too Many Requests
    Content-Type: application/json
    Retry-After: 30
    
    {
        "type": "rate-limit-exceeded",
        "title": "Request rate exceeded",
        "status": 429,
        "detail": "Retry after the indicated delay."
    }

    The rate-limit contract should define:

    • The limiting identity
    • The operation or resource scope
    • The measured time window
    • Response status
    • Retry guidance
    • Whether limits differ by plan or caller
    • How batch operations are counted

    Retry Behaviour

    A client should retry only when the operation, status and contract make retrying safe.

    Retry candidate
          |
          v
    Is the operation idempotent
    or protected by an idempotency key?
          |
          +--> No:
          |       do not retry automatically
          |
          +--> Yes:
                  apply bounded retry
                  with backoff and jitter

    Retry policies should include:

    • Maximum attempts
    • Complete operation deadline
    • Exponential backoff
    • Randomized jitter
    • Retryable statuses
    • Idempotency requirements
    • Cancellation handling

    Timeouts and Deadlines

    Every outbound API request should have a finite deadline.

    A timeout design can distinguish:

    • DNS timeout
    • Connection timeout
    • TLS handshake timeout
    • Request write timeout
    • Response read timeout
    • Complete operation deadline

    Uncertain outcome: A client timeout does not prove that the server failed to complete the operation. State-changing APIs need idempotency or operation-status mechanisms for safe recovery.

    PHP REST Controller Example

    <?php
    
    declare(strict_types=1);
    
    header(
        'Content-Type: application/json; charset=utf-8'
    );
    
    function sendJson(
        int $status,
        array $payload = []
    ): never {
        http_response_code(
            $status
        );
    
        if ($status !== 204) {
            echo json_encode(
                $payload,
                JSON_THROW_ON_ERROR
            );
        }
    
        exit;
    }
    
    function readJsonBody(): array
    {
        $contentType =
            $_SERVER['CONTENT_TYPE'] ?? '';
    
        if (
            !str_starts_with(
                strtolower($contentType),
                'application/json'
            )
        ) {
            sendJson(
                415,
                [
                    'type' =>
                        'unsupported-media-type',
                    'title' =>
                        'Unsupported media type',
                    'status' => 415,
                    'detail' =>
                        'Use application/json.'
                ]
            );
        }
    
        $rawBody =
            file_get_contents(
                'php://input'
            );
    
        if ($rawBody === false) {
            sendJson(
                400,
                [
                    'type' => 'invalid-request',
                    'title' =>
                        'Unable to read request',
                    'status' => 400
                ]
            );
        }
    
        try {
            $data =
                json_decode(
                    $rawBody,
                    true,
                    512,
                    JSON_THROW_ON_ERROR
                );
        } catch (
            JsonException $exception
        ) {
            sendJson(
                400,
                [
                    'type' => 'invalid-json',
                    'title' => 'Invalid JSON',
                    'status' => 400,
                    'detail' =>
                        'The request body is not valid JSON.'
                ]
            );
        }
    
        if (!is_array($data)) {
            sendJson(
                400,
                [
                    'type' =>
                        'invalid-request-body',
                    'title' =>
                        'Invalid request body',
                    'status' => 400
                ]
            );
        }
    
        return $data;
    }
    
    $method =
        $_SERVER['REQUEST_METHOD'];
    
    if ($method !== 'POST') {
        header(
            'Allow: POST'
        );
    
        sendJson(
            405,
            [
                'type' =>
                    'method-not-allowed',
                'title' =>
                    'Method not allowed',
                'status' => 405
            ]
        );
    }
    
    $request =
        readJsonBody();
    
    $name =
        trim(
            (string)($request['name'] ?? '')
        );
    
    $email =
        trim(
            (string)($request['email'] ?? '')
        );
    
    $errors = [];
    
    if ($name === '') {
        $errors[] = [
            'field' => 'name',
            'code' => 'required',
            'message' =>
                'Name is required.'
        ];
    }
    
    if (
        !filter_var(
            $email,
            FILTER_VALIDATE_EMAIL
        )
    ) {
        $errors[] = [
            'field' => 'email',
            'code' => 'invalid_format',
            'message' =>
                'Enter a valid email address.'
        ];
    }
    
    if ($errors !== []) {
        sendJson(
            422,
            [
                'type' =>
                    'validation-error',
                'title' =>
                    'Request validation failed',
                'status' => 422,
                'errors' => $errors
            ]
        );
    }
    
    /*
     * Authorize the operation and insert the
     * customer through a parameterized query.
     */
    
    $customerId = 42;
    
    header(
        'Location: /customers/' .
        $customerId
    );
    
    sendJson(
        201,
        [
            'id' => $customerId,
            'name' => $name,
            'email' => $email,
            'status' => 'active'
        ]
    );

    The example demonstrates content-type validation, safe JSON parsing, field-level validation and consistent responses. Production code still requires authentication, authorization, persistence, conflict handling, logging, idempotency and rate limiting.

    Test REST APIs with curl

    Retrieve a Resource

    curl -i \
      -H 'Accept: application/json' \
      https://api.example.com/customers/42

    Create a Resource

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      -H 'Idempotency-Key: test-request-1042' \
      --data '{"name":"Example Customer","email":"customer@example.com"}' \
      https://api.example.com/customers

    Apply a Partial Update

    curl -i \
      -X PATCH \
      -H 'Content-Type: application/merge-patch+json' \
      -H 'If-Match: "customer-42-v7"' \
      --data '{"status":"inactive"}' \
      https://api.example.com/customers/42

    Delete a Resource

    curl -i \
      -X DELETE \
      https://api.example.com/customers/42

    Replace example hosts, identifiers and credentials with approved test values. Avoid placing production credentials directly in shell history.

    OpenAPI Contract

    A machine-readable API description can document operations, parameters, schemas, responses and security requirements.

    openapi: 3.1.0
    info:
      title: Customer API
      version: 1.0.0
    
    paths:
      /customers/{customerId}:
        get:
          operationId: getCustomer
          parameters:
            - name: customerId
              in: path
              required: true
              schema:
                type: integer
                minimum: 1
          responses:
            "200":
              description: Customer found
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/Customer"
            "404":
              description: Customer not found
    
    components:
      schemas:
        Customer:
          type: object
          required:
            - id
            - name
            - status
          properties:
            id:
              type: integer
            name:
              type: string
            status:
              type: string
              enum:
                - active
                - inactive

    The description should match actual runtime behaviour. Contract tests can detect differences between documentation and implementation.

    Observability

    Useful API telemetry includes:

    • Request rate
    • Latency percentiles
    • Status-code distribution
    • Authentication failures
    • Authorization rejections
    • Validation failures
    • Rate-limit rejections
    • Idempotency-key reuse
    • Dependency latency
    • Request and response sizes

    Logs should avoid recording passwords, access tokens, session identifiers, complete payment information or other sensitive data.

    REST Troubleshooting Workflow

    Troubleshooting Flow
    confirm endpoint → inspect request → check authentication → check authorization → inspect response → trace dependencies
    1. Confirm the HTTP method and complete URI.
    2. Confirm the request content type.
    3. Validate the request body and parameters.
    4. Confirm authentication credentials.
    5. Confirm resource-level authorization.
    6. Record the status code and error response.
    7. Use the correlation identifier to inspect protected logs.
    8. Measure DNS, connection, TLS and server timing separately.
    9. Inspect database and dependency operations.
    10. Check rate limits, retries and idempotency records.
    11. Compare actual behaviour with the documented contract.

    Common REST API Mistakes

    1

    Using Verbs in Every URI

    Model stable resources through nouns and use HTTP methods to express standard operations.

    2

    Changing State through GET

    GET is a safe method and should not request a business-state change.

    3

    Returning 200 for Every Outcome

    Use status codes consistently so clients can distinguish success, validation, authorization, conflict and server failures.

    4

    Using Inconsistent Error Structures

    A stable error contract allows clients to handle failures predictably.

    5

    Returning Unlimited Collections

    Large unbounded responses increase database, memory, network and client processing cost.

    6

    Trusting Client-supplied Ownership Fields

    Determine ownership and authorization from trusted identity and server-controlled resource state.

    7

    Retrying POST Blindly

    A timeout can occur after the server completes the operation. Use an idempotency contract for retryable creation and payment operations.

    8

    Exposing Internal Exceptions

    Stack traces, SQL statements and internal paths should not be returned to API clients.

    9

    Ignoring Concurrent Updates

    Use resource versions, validators or transactional controls where lost updates are possible.

    10

    Breaking Existing Clients with Field Changes

    Treat field names, types, nullability and semantics as part of the public contract.

    11

    Confusing Authentication with Authorization

    A valid credential does not grant access to every resource or operation.

    12

    Logging Credentials and Sensitive Payloads

    Apply explicit redaction and data-minimization rules to logs and traces.

    Recommended Test Cases

    Test Expected Evidence
    Valid resource retrieval The intended representation and status are returned
    Missing resource The documented not-found response is returned
    Invalid JSON The request is rejected through the standard error contract
    Unsupported media type The API returns the documented media-type failure
    Unauthorized request Missing or invalid credentials are rejected
    Forbidden resource An authenticated caller cannot access an unauthorized resource
    Duplicate POST retry The idempotency contract prevents duplicate effects
    Concurrent update A stale precondition is rejected
    Pagination boundary No item is unexpectedly duplicated or omitted under the defined ordering
    Rate limit The documented limit response and retry guidance are returned
    Dependency failure The API fails safely without exposing internal diagnostics
    Contract validation Runtime responses conform to the published API schema

    REST API Best Practices

    Recommended Practices

    • Model important concepts as resources.
    • Use stable, predictable and noun-based resource URIs.
    • Use HTTP methods according to their defined semantics.
    • Return meaningful HTTP status codes.
    • Use a consistent machine-readable error contract.
    • Validate media types, schemas and business rules.
    • Authenticate every protected request.
    • Authorize every operation at the resource level.
    • Use HTTPS for API communication.
    • Paginate collection resources.
    • Define stable sorting before implementing cursor pagination.
    • Use validators to prevent lost updates.
    • Use idempotency keys for safely retryable non-idempotent operations.
    • Apply bounded retries with backoff and jitter.
    • Set finite request deadlines.
    • Document rate-limit semantics.
    • Prefer additive, backward-compatible contract evolution.
    • Maintain a machine-readable API description.
    • Test runtime behaviour against the published contract.
    • Redact credentials and sensitive content from telemetry.

    Practice Exercise

    Design and implement a versioned REST API for customer and order resources.

    Requirements

    1. Create customer collection and item resources.
    2. Create order collection and item resources.
    3. Use GET, POST, PUT, PATCH and DELETE appropriately.
    4. Validate JSON media types.
    5. Return a consistent error representation.
    6. Require authentication for protected operations.
    7. Authorize access at customer and order level.
    8. Support cursor pagination for order collections.
    9. Support filtering by order status.
    10. Apply stable ordering.
    11. Use ETags for conditional retrieval and updates.
    12. Use idempotency keys for order creation.
    13. Apply request-size and rate limits.
    14. Publish an OpenAPI contract.
    15. Create automated contract and authorization tests.

    Endpoint Design

    Method Endpoint Purpose
    GET /v1/customers List customers through bounded pagination
    POST /v1/customers Create a customer
    GET /v1/customers/{customerId} Retrieve one customer
    PUT /v1/customers/{customerId} Replace customer state
    PATCH /v1/customers/{customerId} Modify selected customer fields
    DELETE /v1/customers/{customerId} Delete or deactivate a customer according to contract
    GET /v1/customers/{customerId}/orders List the customer's orders
    POST /v1/orders Create an order using an idempotency key
    GET /v1/orders/{orderId} Retrieve one order

    Frequently Asked Questions

    1

    What is REST?

    REST is an architectural style for distributed systems based on resources, representations, uniform interactions, stateless requests and other architectural constraints.

    2

    Is every JSON API a REST API?

    No. JSON is a representation format. REST concerns resource modelling, interaction semantics and architectural constraints.

    3

    Should REST endpoint names use verbs?

    Resource endpoints normally use nouns, while HTTP methods express standard operations. Domain actions can be modelled as action or process resources when they do not fit ordinary create, retrieve, replace or delete semantics.

    4

    What is the difference between PUT and PATCH?

    PUT creates or replaces the target resource's state using the supplied representation. PATCH applies a partial modification according to its defined patch semantics.

    5

    What does stateless mean in REST?

    Each request contains the information needed to understand and process it without depending on undocumented conversational state from an earlier request.

    6

    What is an idempotent operation?

    An idempotent operation has the same intended effect when the identical request is applied once or several times.

    7

    Should POST requests be retried automatically?

    Not unless the operation supports a documented idempotency mechanism or another contract that makes the retry safe.

    8

    What status should creation return?

    A successful creation can return 201 Created and identify the new resource through Location.

    9

    Why should collection endpoints be paginated?

    Pagination bounds database, memory, serialization, network and client-side processing cost.

    10

    What is an ETag?

    An ETag is a validator that can support cache revalidation and conditional updates.

    11

    Does authentication provide authorization?

    No. Authentication establishes a caller identity. Authorization determines whether that caller may perform the requested operation.

    12

    What comes after REST?

    The next topic is RPC and gRPC, followed by GraphQL and resource modelling.

    Key Takeaway

    REST APIs model application concepts as resources and use HTTP methods, fields, representations and status codes consistently. A production API contract should define validation, errors, authentication, resource-level authorization, pagination, caching, conditional updates, retries, idempotency, rate limits and compatibility. Keep requests self-contained, use HTTPS, bound all collections and payloads, document the contract in a machine-readable format and verify that runtime behaviour matches the published specification.