Skip to content

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.

Return-type inference

A resource, model, or enum return becomes its schema.

Status codes

response()->json($x, 201) folds the payload under 201.

Empty responses

response()->noContent() documents a 204 with no body.

Headers & overrides

Add headers and extra statuses with attributes.

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() (no total/last_page), cursorPaginate(), or the Spatie JSON API Paginate package’s jsonPaginate().
app/Http/Controllers/InvoiceController.php
public function show(Invoice $invoice): InvoiceResource
{
return new InvoiceResource($invoice);
}

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.

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.

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]);
}

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.

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.

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.

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.

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 { /* … */ }