Return-type inference
Responses
Docuccino documents an endpoint’s success responses from the way your action returns — its return type and return statements. You write the code you’d write anyway; the response shapes follow.
Status codes
response()->json($x, 201) folds the payload under 201.Empty responses
response()->noContent() documents a 204 with no body.Headers & overrides
From your return type
Section titled “From your return type”Whatever an action returns is documented as its 200 body. Docuccino resolves the concrete shape
through the same inference that powers everything else:
- an API Resource becomes its resource schema;
- an Eloquent model becomes its model schema;
- a backed enum becomes its enum schema;
- a resource collection becomes an array of that schema, wrapped in the
{ data, links, meta }pagination envelope when the collection paginates —paginate()(length-aware),simplePaginate()(nototal/last_page),cursorPaginate(), or the Spatie JSON API Paginate package’sjsonPaginate().
public function show(Invoice $invoice): InvoiceResource{ return new InvoiceResource($invoice);}"responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/InvoiceResource" } }, "required": ["data"] } } } }}A top-level resource is wrapped under data (Laravel’s default $wrap); the InvoiceResource
component it references is defined once and shared. Control or disable the wrapping with the
api_resources.wrap option — see resource wrapping.
Each response’s description is the standard reason phrase for its status — 200 OK,
201 Created, 202 Accepted, 204 No Content, and so on — so you don’t hand-write them. Responses
are emitted in ascending status order, whatever order your return paths appear in.
Media types
Section titled “Media types”A response is application/json unless the payload itself says otherwise. A first-party JSON:API
resource (or a collection of one) serializes as application/vnd.api+json, as does a
timacdonald/json-api resource. Every payload under a status
has to agree: if one return path there is a plain JSON payload, the response is application/json.
Status codes and JsonResponse
Section titled “Status codes and JsonResponse”Return response()->json($payload, $status) and Docuccino folds the payload shape under the status
you passed — the JsonResponse wrapper itself is never rendered as a generic object. Distinct return
paths with distinct statuses become distinct responses on the same operation:
public function store(StoreInvoiceRequest $request): JsonResponse{ $invoice = Invoice::create($request->validated());
return response()->json(new InvoiceResource($invoice), 201); // → 201 with the resource body}Two return paths with two statuses become two responses:
public function queue(QueueReportRequest $request): JsonResponse{ if ($request->boolean('async')) { return response()->json(['status' => 'accepted'], 202); }
return response()->json(['id' => $this->reports->build($request)->id]);}"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "integer" } }, "required": ["id"] } } }},"202": { "description": "Accepted", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "const": "accepted" } }, "required": ["status"] } } }}Literal values in the payload survive as JSON Schema consts, so 'accepted' is documented as the
only value that status returns. Two return paths that share a status contribute one response whose
schema is the union of their shapes.
A status computed at runtime (not a literal) falls back to 200; pin it with
#[Response] when you need an exact code.
Automatic 201 Created
Section titled “Automatic 201 Created”Returning a resource wrapped directly around a freshly created model — new InvoiceResource(Invoice::create(...)),
InvoiceResource::make(Invoice::create(...)), or the same around Invoice::forceCreate(...) — is
documented as 201 Created automatically, matching Laravel’s own behavior: those factory methods set
wasRecentlyCreated, so ResourceResponse::calculateStatus() answers 201. Only that direct
create-wrap is detected; create into a variable first, or use #[Response], when you need the code to be
explicit. An explicit 201 always wins.
Spatie Data success status
Section titled “Spatie Data success status”A Spatie Data response class that overrides calculateResponseStatus()
has that status documented in place of the default 200. Anything that folds to constant ints is read:
return 201;, a class constant like Response::HTTP_CREATED, or a conditional whose arms are all
constants (return $invoice->isPaid() ? 200 : 202;) — a conditional documents the same body under
each status, matching runtime truth. A genuinely computed status (a widened int, a non-constant
expression) leaves the response at 200 and records a
spatie-data.response-status-unresolved info diagnostic rather than guessing.
Empty responses
Section titled “Empty responses”response()->noContent() is documented as a 204 No Content with no response body. A bare void or
never return (with no JsonResponse wrapper) documents nothing on its own — annotate it with
#[Response] if it should appear.
Adding and refining responses
Section titled “Adding and refining responses”Where the code can’t say everything, attributes step in — field by field, so they patch inferred responses without discarding the rest:
| Attribute | Use it to… |
|---|---|
#[Response] |
Add a status, or set the description, type or mediaType of one — repeatable for several statuses. |
#[ResponseHeader] |
Document a response header on a given status (status: 200 by default). |
#[IgnoreResponse] |
Drop an auto-inferred response by status code. |
#[Response(status: 200, type: InvoiceResource::class, description: 'The invoice')]#[Response(status: 404, description: 'Invoice not found')]#[ResponseHeader(name: 'X-RateLimit-Remaining', type: 'integer', description: 'Calls left', status: 200)]public function show(int $id): InvoiceResource { /* … */ }