Production API design makes change and failure explicit. Resource paths express durable concepts, cursor pagination bounds collections, conditional writes prevent lost updates, versioning protects clients, asynchronous operation resources represent long work, and streaming improves feedback without pretending the work is durable or guaranteed to complete.
Prefer /v1/tickets/{ticket_id} over verbs embedded in paths. Domain commands that do not map cleanly to CRUD can become subresources such as /v1/tickets/{id}/resolution or operations such as /v1/ai-runs.
For stable keyset pagination, order by an immutable tie-broken pair such as (created_at DESC, id DESC). Encode the last pair in an opaque, signed cursor. The next query asks for rows after that pair. Enforce a small default and hard maximum.
Fetch one extra row to decide whether a next cursor exists. Every query includes tenant scope.
Include a version in the public representation or emit an ETag. Require If-Match on a state-changing request and update only where both ID and version match. A zero-row update means the client's copy is stale; return a conflict or precondition failure according to the contract.
Create an ai-run record and return 202 Accepted with its URL. The client can poll durable state or subscribe to progress. Use server-sent events for one-way ordered updates with ordinary HTTP semantics; use WebSockets for ongoing two-way interaction. Neither transport replaces durable state, authorization on reconnect, backpressure, heartbeats, or resume strategy.
Prefer additive changes, tolerant readers, deprecation windows, contract tests, and measured client adoption. Create a new major version for incompatible semantics—not every new field. Version model prompts and output schemas separately from the HTTP API.
Streaming tokens can improve perceived latency but may expose unvalidated or unsafe partial output. Buffer or gate content where required, record the final validated artifact, and tell clients whether partial text is provisional. If a connection closes, the durable ai-run still tells the truth.
Specify and test two pages of tickets where a new ticket is inserted between requests. Then specify an AI run that survives an SSE disconnect and resumes from a last-event ID.
Acceptance criteria: no pre-existing ticket is duplicated or skipped; stale writes fail; reconnect never crosses tenants; durable final state remains readable without the stream.