← Services and data · Chapter 1

API contracts: parameters, identity, access, and errors

Define private-draft editing through JSON validation, trusted identity, object permissions, safe errors, timeout retry, pagination, and concurrent version checks, with an offline ownership model.

After this guide, you can

Design a draft API contract and verify rejected requests have no effects, with explicit retry and concurrent-edit rules.

A reading-list endpoint receives a document ID and new text. Checking only login can let one user edit another user's record. Checking only field presence can save an array as text. A contract must define data, identity, resource access, and failure behavior together.

The case edits one synthetic draft owned by one user. The code returns status numbers in memory, without HTTP, token verification, or a database. It demonstrates authorization and mutation boundaries, then identifies requirements for a real service.

Define the operation and its result

Document method, path, parameter locations, request and response shapes, identity, permission, errors, and retry semantics. Specify text type and length and the saved version returned on success. An instruction to update a record is insufficient.

JSON parsing supplies values rather than business validity. Check object shape, required fields, unknown-field policy, and ranges. Bound body size before reading unlimited input. Bytes, string elements, and visible characters can differ, so define the length policy.

OpenAPI describes operations, schemas, and security schemes and supports documentation or tooling. It does not enforce resource permissions. A statement that only owners may edit still requires server-side checks and implementation tests.

Authenticate and authorize separately

Authentication establishes an identity through verified credentials or a session. A submitted owner field does not establish ownership. Obtain the user from trusted identity context and query or constrain the allowed resource scope.

Authorization asks whether that identity may perform this action on this resource. Reading, editing, deletion, and export can have different rules. Being logged in alone does not restrict object access.

Filter both lists and individual operations. A private list does not protect an unrestricted detail endpoint reached by guessing IDs. Include ownership in actual reads or writes. Concurrent changes between checking and mutation also need database-level handling.

Give errors stable meanings

401 indicates missing or invalid authentication; actual HTTP responses need the challenge required by the authentication scheme. 403 represents refusal of an understood request. For private records whose existence should be hidden, a consistent 404 can cover both absent and unauthorized records.

Use stable application codes and safe explanations for invalid parameters. Do not expose database stacks. Apply choices such as 400 or 422 consistently rather than make clients infer failure types from prose. A safe correlation identifier can connect diagnostics.

Hiding existence is not authorization. A rejected request must have no unauthorized effects regardless of its status. Keep diagnostic information separate from public errors and avoid recording credentials or full personal payloads.

Verify ownership and mutations offline

D-1 belongs to U-1. The function checks identity, then record existence and ownership, then text. U-2 receives 404 without changing draft; U-1 changes it to ready.

records = {"D-1": {"owner": "U-1", "text": "draft"}}
def edit(user, record_id, text):
    if user is None:
        return 401
    record = records.get(record_id)
    if record is None or record["owner"] != user:
        return 404
    if not isinstance(text, str) or not 1 <= len(text) <= 100:
        return 400
    record["text"] = text
    return 200
assert edit("U-2", "D-1", "changed") == 404
assert records["D-1"]["text"] == "draft"
assert edit("U-1", "D-1", "ready") == 200
print(records["D-1"]["text"])

The output is ready. The model verifies these access and mutation rules. Length counts Python string elements, and there are no HTTP headers, authentication challenges, byte limits, or concurrent transactions. This is not a complete secure API.

Parameter checks follow authorization in the example. A real service can perform basic size and shape checks earlier to bound resource use. Choose order with information disclosure, cost, and consistent error semantics in mind.

Decide whether retry preserves business effects

HTTP safe means the method's intended semantics do not request resource changes. Idempotent means repeating an identical request has the same intended effect as one request. Incidental logging can still occur. These properties do not automatically deduplicate payments, task creation, or version increments.

A timeout may occur after the server committed but before the client received its response. Define idempotency keys and saved results where repeated effects matter, or provide a final-state query. Unlimited timeout retries can multiply effects and load.

Set retry conditions, attempt limits, total time budgets, and backoff for network or service failures. Handle Retry-After according to the contract where supplied. Permission and parameter errors usually require changed conditions rather than unchanged retries.

Specify pagination and editing versions

Lists need stable ordering and pagination rules, including whether cursors bind filters. Changes between pages can cause repetitions or omissions. Select snapshots, cursors, or deduplication according to requirements and state the conditions.

For simultaneous edits, a submitted version can constrain the update. Reject a stale version with a defined conflict instead of silently overwriting another edit. If last-write-wins is intentional, describe that policy explicitly.

Choose an unknown-field policy deliberately. Rejection can expose misspellings early, while ignoring fields may support gradual extension. Document either choice. For batch edits, define atomic failure or per-item results so a partial write does not leave the client uncertain about recovery.

Exercise: reject without side effects

Define request shape, trusted identity, ownership, error codes, and retry conditions. Test unauthenticated, owner, other user, missing ID, empty text, oversized text, and incorrect types. Compare records before and after every rejection.

Only valid owner input may change the draft. Existence-hiding must be consistent, codes must be machine-readable, timeouts must support status lookup or safe retry, and stale edits must not silently overwrite. The offline checks cover simplified rules; verify authentication, HTTP responses, and database commits independently in a real endpoint.

Sources & further reading

Sources checked on 2026-11-10. Original engineering explanation; examples are teaching data, not production measurements.