This page contains additional examples of how to apply various parts of the specification.
Sparse Fieldsets
Examples of how sparse fieldsets work.
Basic request:
GET /articles?include=author HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2015-05-22T14:56:29.000Z",
"updated": "2015-05-22T14:56:28.000Z"
},
"relationships": {
"author": {
"data": {"id": "42", "type": "people"}
}
}
}],
"included": [
{
"type": "people",
"id": "42",
"attributes": {
"name": "John",
"age": 80,
"gender": "male"
}
}
]
}
Request with fields[articles]
and fields[people]
parameters:
GET /articles?include=author&fields[articles]=title,body,author&fields[people]=name HTTP/1.1
Note: The above example URI shows unencoded
[
and]
characters simply
for readability. In practice, these characters should be percent-encoded, as
noted in the base specification. See “Square Brackets in Parameter Names”.
Here we want articles
objects to have fields title
, body
and author
only and people
objects to have name
field only.
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever."
},
"relationships": {
"author": {
"data": {"id": "42", "type": "people"}
}
}
}],
"included": [
{
"type": "people",
"id": "42",
"attributes": {
"name": "John"
}
}
]
}
Pay attention to the fact that you have to add a relationship name both in include
and fields
(since relationships are fields too), otherwise you’ll get:
GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever."
}
}],
"included": [
{
"type": "people",
"id": "42",
"attributes": {
"name": "John"
}
}
]
}
Note: The above example URI shows unencoded
[
and]
characters simply
for readability. In practice, these characters should be percent-encoded, as
noted in the base specification. See “Square Brackets in Parameter Names”.
Example of a page-based strategy on how to add pagination links.
Basic request:
GET /articles?page[number]=3&page[size]=1 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"meta": {
"totalPages": 13
},
"data": [
{
"type": "articles",
"id": "3",
"attributes": {
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2015-05-22T14:56:29.000Z",
"updated": "2015-05-22T14:56:28.000Z"
}
}
],
"links": {
"self": "http://example.com/articles?page[number]=3&page[size]=1",
"first": "http://example.com/articles?page[number]=1&page[size]=1",
"prev": "http://example.com/articles?page[number]=2&page[size]=1",
"next": "http://example.com/articles?page[number]=4&page[size]=1",
"last": "http://example.com/articles?page[number]=13&page[size]=1"
}
}
Note: The above example URIs show unencoded
[
and]
characters simply
for readability. In practice, these characters should be percent-encoded, as
noted in the base specification. See “Square Brackets in Parameter Names”.
Note: Putting a property like
"totalPages"
in"meta"
can be a convenient way
to indicate to clients the total number of pages in a collection (as opposed to
the"last"
link, which simply gives the URI of the last page). However, all
"meta"
values are implementation-specific, so you can call this member whatever
you like ("total"
,"count"
, etc.) or not use it at all.
Error Objects
Examples of how error objects work.
A Basic Error Object
In the response below, the server is indicating that it encountered an error
while creating/updating the resource, and that this error was caused
by an invalid "firstName"
attribute:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json
{
"errors": [
{
"status": "422",
"source": { "pointer": "/data/attributes/firstName" },
"title": "Invalid Attribute",
"detail": "First name must contain at least two characters."
}
]
}
Every member in an error object is optional, but all help the client
by providing extra details.
The source
member is used to indicate
which part of the request document caused the error.
The title
and detail
members are similar, but detail
is specific
to this occurrence of the problem, whereas title
is more generic.
The status
member represents the HTTP status code associated with the problem.
It’s very helpful when multiple errors are returned at once (see below), as the
HTTP response itself can only have one status code. However, it can also be
useful for single errors, to save clients the trouble of consulting the HTTP
headers, or for using JSON:API over non-HTTP protocols, which may be officially
supported in the near future.
Multiple Errors
When multiple errors occur in response to a single request, the server
can simply add each error to the errors
array:
HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json
{
"errors": [
{
"status": "403",
"source": { "pointer": "/data/attributes/secretPowers" },
"detail": "Editing secret powers is not authorized on Sundays."
},
{
"status": "422",
"source": { "pointer": "/data/attributes/volume" },
"detail": "Volume does not, in fact, go to 11."
},
{
"status": "500",
"source": { "pointer": "/data/attributes/reputation" },
"title": "The backend responded with an error",
"detail": "Reputation service not responding after three requests."
}
]
}
The only uniqueness constraint on error objects is the id
field. Thus,
multiple errors on the same attribute can each be given their own error
object. The example below shows multiple errors on the "firstName"
attribute:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json
{
"errors": [
{
"source": { "pointer": "/data/attributes/firstName" },
"title": "Invalid Attribute",
"detail": "First name must contain at least two characters."
},
{
"source": { "pointer": "/data/attributes/firstName" },
"title": "Invalid Attribute",
"detail": "First name must contain an emoji."
}
]
}
Note: in the responses above with a 422 status code,
400 Bad Request
would
also be acceptable. (More details.)
JSON:API doesn’t take a position on 400 vs. 422.
Error Codes
The code
member of an error object contains an application-specific code
representing the type of problem encountered. code
is similar to title
in that both identify a general type of problem (unlike detail
, which is
specific to the particular instance of the problem), but dealing with code
is easier programatically, because the “same” title
may appear in different
forms due to localization.
For the example below, imagine the API docs specifed the following mapping:
Code | Problem |
---|---|
123 | Value too short |
225 | Password lacks a letter, number, or punctuation character |
226 | Passwords do not match |
227 | Password cannot be one of last five passwords |
Multiple errors on "password"
attribute, with error code
:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json
{
"jsonapi": { "version": "1.0" },
"errors": [
{
"code": "123",
"source": { "pointer": "/data/attributes/firstName" },
"title": "Value is too short",
"detail": "First name must contain at least two characters."
},
{
"code": "225",
"source": { "pointer": "/data/attributes/password" },
"title": "Passwords must contain a letter, number, and punctuation character.",
"detail": "The password provided is missing a punctuation character."
},
{
"code": "226",
"source": { "pointer": "/data/attributes/password" },
"title": "Password and password confirmation do not match."
}
]
}
Notice that this response includes not only the errors
top-level member,
but the jsonapi
top-level member. Error responses cannot contain the
top-level data
member, but can include all the other top-level members
JSON:API defines.
Also, notice that the third error object lacks a detail
member (perhaps
for security). Again, all error object members are optional.
Advanced source
Usage
In the example below, the user is sending an invalid JSON:API
request, because it’s missing the data
member:
PATCH /posts/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{ "datum": [ ] }
Therefore, the server responds:
HTTP/1.1 422 Unprocesssable Entity
Content-Type: application/vnd.api+json
{
"errors": [
{
"source": { "pointer": "" },
"detail": "Missing `data` Member at document's top level."
}
]
}
It uses source
to point to the top-level of the document (""
).
(Pointing to “/” would be an appropriate reference to the string
"some value"
in the request document {"": "some value"}
.
Pointing to "/data"
would be invalid because the request document did
not have a value at "/data"
, and source
is always given with reference
to the request document.)
If the server cannot parse the request as valid JSON, including
source
doesn’t make sense (because there’s no JSON document for source
to
refer to). Here’s how the server might respond to an invalid JSON document:
{
"errors": [{
"status": "400",
"detail": "JSON parse error - Expecting property name at line 1 column 2 (char 1)."
}]
}
Invalid Query Parameters
The source
member can also be used to indicate that the error originated
from a problem with a URI query parameter, like so:
GET /api/posts/1?include=author HTTP/1.1
HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json
{
"errors": [
{
"source": { "parameter": "include" },
"title": "Invalid Query Parameter",
"detail": "The resource does not have an `author` relationship path."
}
]
}
In most cases, JSON:API requires the server to return an error when it encounters
an invalid value for a JSON:API–defined query parameter. However, for API-specific
query parameters (i.e. those not defined by JSON:API), a server may choose to
ignore an invalid parameter and have the request succeed, rather than respond with
an error. API-specific query parameters must contain one non a-z
character.
Other examples of invalid parameters include: ?fields[people]=
(invalid parameter name;
should be fields[people]
) and ?redirect_to=http%3A%2F%2Fwww.owasp.org
(invalid parameter,
in this case, a phishing attack), etc.
- Introduction
- Error Objects
- Error Lists
- Responses
- HTTP Status
- JSON:API Exceptions
- Helper Methods
- Validation Errors
- Source Pointers
- Error Rendering
- JSON Responses
- Middleware Responses
- Always Rendering JSON:API Errors
- Custom Rendering Logic
- Converting Exceptions
- Error Reporting
# Introduction
The JSON:API specification defines error objects (opens new window)
that are used to provide information to a client about problems encountered
while performing an operation.
Errors are returned to the client in the top-level errors
member of the
JSON document.
Laravel JSON:API makes it easy to return errors to the client — either
as responses, or by throwing exceptions. In addition, the exception
renderer you added to your exception handler during
installation takes care of
converting standard Laravel exceptions to JSON:API error responses
if the client has sent an Accept: application/vnd.api+json
header.
# Error Objects
Use our LaravelJsonApiCoreDocumentError
object to create a JSON:API
error object. The easiest way to construct an error object is using the
static fromArray
method. For example:
The fromArray
method accepts all the error object members
defined in the specification. (opens new window)
Alternatively, if you want to use setters, use the static make
method
to fluently construct your error object:
The available setters are:
setAboutLink
setCode
setDetail
setId
setLinks
setMeta
setStatus
setSource
setSourceParameter
setSourcePointer
setTitle
# Error Lists
If you need to return multiple errors at once, use our
LaravelJsonApiCoreDocumentErrorList
class. This accepts any number
of error objects to its constructor. For example:
Use the push
method to add errors after constructing the error list:
# Responses
Both the Error
and ErrorList
classes implement Laravel’s Responsable
interface. This means you can return them directly from controller actions
and they will be converted to a JSON:API error response.
If you need to customise the error response, then you need to use our
LaravelJsonApiCoreResponsesErrorResponse
class. Either create
a new one, passing in the Error
or ErrorList
object:
Or alternatively, use the prepareResponse
method on either the Error
or ErrorList
object:
The ErrorResponse
class has all the helper methods
required to customise both the headers and the JSON:API document that
is returned in the response.
For example, if we were adding a header and meta to our response:
# HTTP Status
The JSON:API specification says:
When a server encounters multiple problems for a single request,
the most generally applicable HTTP error code SHOULD be used in the response.
For instance,400 Bad Request
might be appropriate for multiple 4xx errors
or500 Internal Server Error
might be appropriate for multiple 5xx errors.
Our ErrorResponse
class takes care of calculating the HTTP status for you.
If there is only one error, or all the errors have the same status, then
the response status will match this status.
If you have multiple errors with different statuses, the response status
will be 400 Bad Request
if the error objects only have 4xx
status codes.
If there are any 5xx
status codes, the response status will be
500 Internal Server Error
.
If the response has no error objects, or none of the error objects have a
status, then the response will have a 500 Internal Server Error
status.
If you want the response to have a specific HTTP status, use the
withStatus
method. For example:
# JSON:API Exceptions
Our LaravelJsonApiCoreExceptionsJsonApiException
allows you to terminate
processing of a request by throwing an exception with JSON:API errors
attached.
The exception expects its first argument to be either an Error
or an
ErrorList
object. For example:
The JsonApiException
class has all the helper methods
required to customise both the headers and the JSON:API document that
is returned in the response. Use the static make
method if you need to
call any of these methods. For example:
There is also a handy static error
method. This allows you to fluently
construct an exception for a single error, providing either an Error
object or an array. For example:
# Helper Methods
The JsonApiException
class has a number of helper methods:
- is4xx
- is5xx
- getErrors
# is4xx
Returns true
if the HTTP status code is a client error, i.e. in the 400-499
range.
# is5xx
Returns true
if the HTTP status code is a server error, i.e. in the 500-599
range.
# getErrors
Use the getErrors()
method to retrieve the JSON:API error objects from the
exception. For example, if we wanted to log the errors:
# Validation Errors
Our implementation of resource requests and
query parameter requests already takes
care of converting Laravel validation error messages to JSON:API errors.
If however you have a scenario where you want to convert a failed validator
to JSON:API errors manually, we provide the ability to do this.
You will need to resolve an instance of LaravelJsonApiValidationFactory
out of the service container. For example, you could use the app
helper,
or use dependency injection by type-hinting it in a constructor of a service.
Once you have the factory instance, use the createErrors
method,
providing it with the validator instance. For example, in a controller
action:
The object this returns is Responsable
— so you can return it directly
from a controller action. If you want to convert it to an error response,
use our prepareResponse
pattern as follows:
# Source Pointers
By default this process will convert validation error keys to JSON source
pointers. For example, if you have a failed message for the foo.bar
value, the resulting error object will have a source pointer of
/foo/bar
.
If you need to prefix the pointer value, use the withSourcePrefix
method.
The following example would convert foo.bar
to /data/attributes/foo/bar
:
If you need to fully customise how the validation key should be converted,
provide a Closure
to the withPointers
method:
# Error Rendering
As described in the installation instructions,
the following should have been added to the register
method on your
application’s exception handler:
The Laravel exception handler already takes care of converting exceptions to
either application/json
or text/html
responses. Our exception handler
effectively adds JSON:API error responses as a third media type. If the
client has sent a request with an Accept
header of application/vnd.api+json
,
then they will receive the exception response as a JSON:API error response —
even if the endpoint they are hitting is not one of your JSON:API server
endpoints.
WARNING
There are some scenarios where the Laravel exception handler does not call
any registered renderables. For example, if an exception implements Laravel’s
Responsable
interface, our exception parser will not be invoked as the handler
uses the Responsable::toResponse()
method on the exception to generate a
response.
# JSON Responses
If a client encounters an exception when using an Accept
header of
application/json
, they will still receive Laravel’s default JSON exception
response, rather than a JSON:API response.
If you want our exception parser to render JSON exception responses instead
of the default Laravel response, use the acceptsJson()
method when registering
our exception parser:
# Middleware Responses
Sometimes you may want exceptions to be converted to JSON:API errors if the
current route has a particular middleware applied to it. The most common example
of this would be if you want JSON:API errors to always be rendered if the
current route has the api
middleware.
In this scenario, use the acceptsMiddleware()
method when registering our
exception parser. For example:
TIP
You can provide multiple middleware names to the acceptsMiddleware()
method.
When you do this, it will match a route that contains any of the provided
middleware.
# Always Rendering JSON:API Errors
If you want our exception parser to always convert exceptions to JSON:API
errors, use the acceptsAll()
helper method:
# Custom Rendering Logic
If you want your own logic for when a JSON:API exception response should be
rendered, pass a closure to the accept()
method.
For example, let’s say we wanted our API to always return JSON:API exception
responses, regardless of what Accept
header the client sent. We would use
the request is()
method to check if the path is our API:
TIP
If you return false
from the callback, the normal exception rendering logic
will run — meaning a client that has sent an Accept
header with the JSON:API
media type will still receive a JSON:API response. This is semantically correct,
as the Accept
header value should be respected.
# Converting Exceptions
Our exception parser is built so that you can easily add support for
custom exceptions to the JSON:API rendering process. The implementation works
using a pipeline, meaning you can add your own handlers for converting
exceptions to JSON:API errors.
For example, imagine our application had a PaymentFailed
exception, that
we wanted to convert to JSON:API errors if thrown to the exception handler.
We would write the following class:
We can then add it to the JSON:API exception parser using either the
prepend
or append
method:
# Error Reporting
As described in the installation instructions,
the following should have been added to the $dontReport
property on your
application’s exception handler:
This prevents our JsonApiException
from being reported in your application’s
error log. This is a sensible starting position, as the JsonApiException
class is effectively a HTTP exception that needs to be rendered to the client.
However, this does mean that any JsonApiException
that has a 5xx
status
code (server-side error) will not be reported in your error log. Therefore,
an alternative is to use the helper methods on the JsonApiException
class
to determine whether or not the exception should be reported.
To do this, we will use the reportable()
method to register a callback
for the JSON:API exception class. (At the same time, we remove the exception
class from the $dontReport
property.) For example, the following will stop
the propagation of JSON:API exceptions to the default logging stack if the
exception does not have a 5xx status:
In the following example, we log 4xx statuses as debug information, while
letting all other JSON:API exceptions propagate to the default logging stack:
REST API response format based on some of the best practices
Rest API Popular Endpoint Formats
https://api.example.com/v1/items
https://example.com/api/v1/items
Rest API Success Responses
1- GET — Get single item — HTTP Response Code: 200
HTTP/1.1 200 Content-Type: application/json { "id": 10, "name": "shirt", "color": "red", "price": "$23" }
2- GET — Get item list — HTTP Response Code: 200
HTTP/1.1 200 Pagination-Count: 100 Pagination-Page: 5 Pagination-Limit: 20 Content-Type: application/json [ { "id": 10, "name": "shirt", "color": "red", "price": "$123" }, { "id": 11, "name": "coat", "color": "black", "price": "$2300" } ]
3- POST — Create a new item — HTTP Response Code: 201
HTTP/1.1 201 Location: /v1/items/12 Content-Type: application/json { "message": "The item was created successfully" }
4- PUT — Update an item — HTTP Response Code: 200/204
If updated entity is to be sent after the update
HTTP/1.1 200 Content-Type: application/json { "id": 10, "name": "shirt", "color": "red", "price": "$23" }
If updated entity is not to be sent after the update
5- DELETE — Delete an item — HTTP Response Code: 204
Rest API Error Responses
1- GET — HTTP Response Code: 404
HTTP/1.1 404 Content-Type: application/json { "message": "The item does not exist" }
2- DELETE — HTTP Response Code: 404
HTTP/1.1 404 Content-Type: application/json { "message": "The item does not exist" }
3- POST — HTTP Response Code: 400
HTTP/1.1 400 Content-Type: application/json { "message": "Validation errors in your request", /* skip or optional error message */ "errors": [ { "message": "Oops! The value is invalid", "code": 34, "field": "email" }, { "message": "Oops! The format is not correct", "code": 35, "field": "phoneNumber" } ] }
4- PUT — HTTP Response Code: 400/404
HTTP/1.1 400 Content-Type: application/json { "message": "Validation errors in your request", /* skip or optional error message */ "errors": [ { "message": "Oops! The format is not correct", "code": 35, "field": "phoneNumber" } ] } HTTP/1.1 404 Content-Type: application/json { "message": "The item does not exist" }
5- VERB Unauthorized — HTTP Response Code: 401
HTTP/1.1 401 Content-Type: application/json { "message": "Authentication credentials were missing or incorrect" }
6- VERB Forbidden — HTTP Response Code: 403
HTTP/1.1 403 Content-Type: application/json { "message": "The request is understood, but it has been refused or access is not allowed" }
7- VERB Conflict — HTTP Response Code: 409
HTTP/1.1 409 Content-Type: application/json { "message": "Any message which should help the user to resolve the conflict" }
8- VERB Too Many Requests — HTTP Response Code: 429
HTTP/1.1 429 Content-Type: application/json { "message": "The request cannot be served due to the rate limit having been exhausted for the resource" }
9- VERB Internal Server Error — HTTP Response Code: 500
HTTP/1.1 500 Content-Type: application/json { "message": "Something is broken" }
10- VERB Service Unavailable — HTTP Response Code: 503
HTTP/1.1 503 Content-Type: application/json { "message": "The server is up, but overloaded with requests. Try again later!" }
Validation Error Formats
Validation error formats can be different depending on your requirements. Following are some other popular formats, other than the one used above.
HTTP/1.1 400 Content-Type: application/json { "message": "Validation errors in your request", /* skip or optional error message */ "errors": { "email": [ "Oops! The email is invalid" ], "phoneNumber": [ "Oops! The phone number format is not correct" ] } }
HTTP/1.1 400 Content-Type: application/json { "message": "Validation errors in your request", /* skip or optional error message */ "errors": { "email": [ { "message": "Oops! The email is invalid", "code": 35 } ], "phoneNumber": [ { "message": "Oops! The phone number format is not correct", "code": 36 } ] } }
References
Avoid using ‘X-‘ in custom headers: https://tools.ietf.org/html/rfc6648