From a customer's perspective, creating a reading list should be simple: save it, receive confirmation, and open it. That interaction depends on several promises from the API: the list exists, its owner is correct, the service has applied defaults, and the returned address identifies the saved resource. A timeout introduces another question: would trying again create a second list?
A create API defines how new resources enter a system.
This chapter uses a fictional bookstore API to explain request design, successful responses, validation, permissions, conflicts, and incomplete outcomes. The examples assume an authenticated JSON API that clients use over HTTPS.
Begin with the business result. For the bookstore, creating a reading list establishes an empty collection that belongs to the authenticated customer. The list has a server-assigned ID, a name, a visibility setting, and a creation timestamp. Adding books is a separate operation.
The endpoint is POST /reading-lists. Posting to a collection is a common convention for creation when the server chooses the new resource's address. POST also supports other processing; the method alone does not define what this endpoint creates.
A client that chooses the target address might instead use PUT if creating or replacing the representation at that address fits the contract. Choosing an address also requires explicit rules about existing resources and overwrite protection. This reading-list API uses server-assigned addresses and does not support replacement through its create endpoint.
Before implementing the handler, define when creation counts as successful. Here, creation succeeds only when the service saves the list and its ownership record together and the owner can retrieve the list at the returned address. It does not mean the list contains books or has appeared in every search index.
The flow separates the client's proposed values from the service's responsibilities:
The client requests an outcome. The service determines whether its rules permit that outcome, applies its rules, and returns the resulting resource. A database insert is only one part of that contract.
Design a create request separately from the response representation. A client should not have to invent an ID, copy a timestamp, or submit an ownership field merely because those values appear in responses.
This endpoint accepts a JSON object with the following rules:
The length rule is an example contract, not a universal way to measure names. Clients and servers must agree on the unit; visible characters and Unicode code points are not always the same thing.
Omission and explicit values need separate meanings. Omitting visibility selects the documented default. Sending null, an empty string, or "Private" is invalid in this API. An empty name does not ask the service to generate one.
This API rejects unknown fields and server-managed fields with a validation error. For example, visiblity should produce useful feedback rather than silently create a private list. This is a design choice: other APIs may ignore unknown input fields, but the API team must choose and document the behavior deliberately.
Map accepted fields explicitly. Binding every JSON property directly onto a stored object can let a caller assign fields such as ownership or administrative state that the API never intended clients to write.
Defaults are public behavior. Changing the omitted visibility from private to public would change what existing clients create even though their requests remain identical. Preserve established defaults or evolve the contract explicitly.
The customer sends the following request to https://api.bookstore.example:
The service saves the list and returns its initial representation:
Each JSON body occupies one line without a trailing newline. Tokens and identifiers are illustrative.
201 Created reports completed creation. For a POST that creates a resource, HTTP recommends a Location identifying the primary new resource. This API always includes it. The relative address above resolves against the API origin and points to the individual list, not the collection.
Returning the saved representation is useful because it shows the assigned identity, applied defaults, and any documented normalization. Clients can display the list immediately without reconstructing those values or making an extra read request. A response body is an API choice, rather than a requirement of every 201 response.
Keep the body and address consistent: list_801 in the representation is the list at /reading-lists/list_801. Return only fields the caller may read. This example uses Cache-Control: no-store because the response contains customer-specific data.
The bookstore also promises that an immediate authorized GET to that address can retrieve the list. That visibility guarantee comes from this API's contract. If writes and reads use different storage replicas, the implementation must account for replication delay to honor it. Search visibility can follow a different documented schedule.
Validation asks whether the proposed resource satisfies the create contract. Authorization asks whether the caller may create it in the requested scope. A request must satisfy both before the service makes the resource visible.
For example, a customer submits a name that becomes empty after trimming:
The API rejects it without creating a list:
The error envelope and field codes are this API's conventions. Clients can use validation_failed and required for program behavior while displaying the message to a person.
For this endpoint, malformed JSON produces 400 Bad Request, an unsupported request media type produces 415 Unsupported Media Type, and well-formed JSON with invalid field values produces 422 Unprocessable Content. This is a consistent policy, not a claim that every API must divide validation errors the same way. The service also limits request bodies to 8 KiB and returns 413 Content Too Large when a body exceeds that limit.
Now assume a support token allows reading customer lists but not creating them. A valid request still fails:
The service creates no list. A missing or invalid credential is a separate authentication failure; this API uses 401 Unauthorized with an appropriate WWW-Authenticate challenge.
Permissions also apply to referenced resources. If an endpoint creates a list inside a shared workspace, the service must verify permission to create there. Knowing the workspace ID or being able to read it is insufficient. Derive the destination from the authorized request context and check any body-supplied references within that scope.
Decide which failures callers may observe. The API may conceal a private workspace with 404 Not Found; returning a detailed conflict about its contents could reveal information. Do not expose database errors or sensitive submitted values to explain a rejection.
Two resources with identical fields are not necessarily duplicates. This bookstore allows a customer to own two reading lists with the name Systems Reading. A name is display text, so repeating it is not a conflict and is not evidence that an earlier request succeeded.
Other creates have real uniqueness rules. Consider a supplier integration that creates catalog entries through POST /suppliers/sup_17/catalog-entries. The supplier must be accessible to the caller. Within that supplier, external_key is an immutable, case-sensitive string that may identify only one catalog entry. The service preserves that mapping while the integration remains active.
The supplier submits a key an entry already uses:
This endpoint's create-only policy rejects an occupied key, even if the submitted fields match:
The existing entry remains unchanged. Silently updating it would turn a create operation into a create-or-update operation, which needs a different contract.
Enforce uniqueness at the point that coordinates competing writes. A handler that checks for a key and inserts later can race with another handler. A storage uniqueness constraint or equivalent atomic mechanism must decide which request succeeds.
The diagram shows two authorized requests competing for the same supplier key:
Only one request can claim the key. Either request may win; the API does not promise arrival ordering. A collision in a server-generated ID is different: the service should normally generate another candidate internally rather than ask the customer to repair an ID they did not choose.
A create request can require several writes. Saving a reading list may also require saving its owner relationship and recording work for a search index. Decide which changes constitute creation and which can finish afterward.
For this API, the list and ownership relationship form one atomic unit: the service commits both or neither. Returning success for an ownerless list would violate the resource model. If a request includes several initial child records, define whether they all succeed together; do not leave that decision to whichever insert happens to fail first.
Search indexing is follow-up work. The service can commit the list and a durable record of required indexing work together, then process that work afterward. This avoids losing the indexing request if the process stops immediately after saving the list. The exact mechanism is an implementation choice.
A delayed search update should not turn completed creation into a failure response. Otherwise, a client may retry and create another list even though the first exists. Conversely, a handler must not report completed creation while essential writes are still waiting only in process memory.
A local database transaction does not make remote operations atomic. If creation requires an external payment, approval, or provisioning step, model incomplete work explicitly instead of claiming that all effects succeeded together.
A timeout says that the caller did not receive a usable result. It does not establish whether creation happened. The service might have committed a list before the connection failed.
This sequence illustrates that uncertainty:
Because this API permits repeated names, matching the body does not identify a retry. POST does not provide idempotency by method semantics. An idempotent operation has the same intended effect when the client repeats it as when the client performs it once.
Under the contract so far, the reading-list endpoint has no duplicate-request protection. Its clients must not treat an uncertain result as proof that resubmission is harmless. Searching by name is not a reliable recovery method because multiple lists may legitimately share that name.
For APIs where duplicate creation is costly, provide an explicit recovery contract. One option is an idempotency key: a client-generated value identifying one intended operation across retries. Supporting it requires documented scope, retention, treatment of changed input, and behavior while the first request is still running. Merely accepting a header does not provide those guarantees.
Business uniqueness and request identity solve different problems. A supplier key prevents two catalog entries from claiming the same business reference. A request key can let the client recover the outcome of its original submission. Neither should silently authorize access to another caller's resource.
The service can create some resources immediately even if the work they represent takes longer. For others, it must wait before reporting that creation has finished. Choose the response according to what the endpoint promises to create.
For example, POST /catalog-imports can save an import request durably with its own ID and a pending state. Returning 201 Created then means the import request exists. It does not mean the service has imported its catalog entries.
If an endpoint instead accepts work to create a resource later, 202 Accepted communicates acceptance while processing remains incomplete. Provide a status resource so callers can discover success or failure. The following is an illustrative response for an accepted catalog-build request:
Here, operation_id, status, and status_url are application-defined fields. This API makes the status address accessible to the requesting caller and eventually records either the created resource's address or a failure reason. Its contract must also specify how long that result remains available.
Do not select 202 merely because an email or search update runs in the background. If the promised resource already exists with its required state, creation may be complete. If essential work is pending, show that honestly through the resource model and response.
Design creation around a precise result: what resource exists, who owns it, which fields clients may choose, and what success guarantees. Return the saved identity, applied defaults, and resource address so clients can use the result immediately.
Validate input and permissions before committing, enforce business uniqueness under concurrent requests, and keep required writes consistent. Define how callers recover from uncertain outcomes, and distinguish completed creation from work the service has only accepted.