JSON endpoints
The contract of the editing endpoints, for a client other than the one this extension ships. Everything below is enforced by the server; a client that violates it receives one of the status codes in the last section.
How they are addressed
The endpoints are a PAGE object of their own, keyed on a page
type. They answer on whichever page the edit plugin sits on, so no page has to
be created for them.
| Setting | TypoScript constant | Default |
|---|---|---|
modernextbasefrontendedit. | plugin. | 1589 |
The page object renders the Extbase plugin Ajax of the extension
directly, with config. so the body is
exactly what the action produced, and config..
view. is set to the same number.
Important
The URLs cannot be assembled by a client. The action name travels in
the query string, as
tx_, and is
therefore part of the cHash. A missing or wrong cHash is answered with
404 by TYPO3 before the plugin runs, and a cHash cannot be computed
in a browser.
The server therefore builds one finished URL per endpoint and hands the map
to the client. The plugin shipped with this extension renders it as JSON
into the data- attribute of its markup, next to the
request token in data-. A second client obtains its URLs the
same way — from the server, never by concatenation.
The map carries eight entries: save, save,
add, remove, reorder,
set, upload and remove.
read is deliberately not among them. When the page type is 0
the map is empty.
The endpoints
All of them are POST. All of them except upload take a
JSON object as the request body.
| Endpoint | Body keys | Writes |
|---|---|---|
read | uid (optional) | Nothing. Returns the caller's profile. |
save | uid, data, and child with
child when a child record is addressed | Every writable field of one record at once. |
save | uid, field, value, and child with
child when a child record is addressed | One named field of one record. |
add | uid, child, data | One new child record, appended last. |
remove | uid, child, child | Deletes the addressed child record. |
reorder | uid, child, order | The sorting of one collection. |
set | uid, child, child, hidden | The hidden state of one child record. |
upload | A multipart/ body — see
Image upload | The profile image. |
remove | uid | Clears the profile image. |
The value types are checked strictly, and a value of the wrong type is a
400 rather than something that is cast:
| Key | Type |
|---|---|
uid, child | A JSON integer greater than 0. A numeric string is refused. |
child | "address" or "email". Absent or null
addresses the profile itself, which save and
save allow and the four collection endpoints do not. |
data | A JSON object. |
field | A non-empty string naming a writable field — see Validation rules. |
value | A string, or null. |
hidden | A JSON boolean. |
order | A JSON array of integers greater than 0. It has to be a
permutation of the whole collection: a wrong length or a
repeated uid is refused before anything is written. |
uid is required by every endpoint except read, and it is only
ever a filter: the set of records a request may reach is resolved from the
session, and a uid outside that set is answered like a uid that does not exist.
The request token
Every writing endpoint requires a TYPO3 request token in the
X- header. It is a hash signed JWT bound to a nonce
cookie of that browser, with the scope
modern_extbase_frontend_edit/record-save
A token that is missing, that cannot be verified, or that carries a different scope is refused identically. The token is proof that the browser loaded a page of this site; it is not authorisation, and it does not replace the login check.
The __ body parameter TYPO3 also accepts is not usable
here: it is read from the parsed request body, which is empty for a request
carrying a JSON body.
read requires no token and no login. It changes nothing, and answering
an anonymous caller differently from a logged-in non-owner would say which
profiles exist.
The guards, in order
For a writing endpoint with a JSON body, each check runs before any value of the request body is looked at:
| Order | Check | Failure |
|---|---|---|
| 1 | The request method is POST. | 405, with an Allow: POST header. |
| 2 | The media type is application/. | 400 |
| 3 | A valid request token of the scope above was received. | 403 |
| 4 | A website user is logged in. | 403 |
| 5 | The request runs in the live workspace. | 409 |
| 6 | The body is empty, or a JSON object. | 400 |
| 7 | The addressed record is in the set the session owns. | 404 |
| 8 | The remaining body keys have the required types. | 400 |
| 9 | The submitted values satisfy the validation rules. | 422 |
read runs steps 1, 2, 6 and 7 only. upload replaces step 2
with multipart/ and adds a check that exactly one file was
sent; its order is otherwise the same.
The response envelope
Every response carries a JSON body and the content type
application/ — successes and failures alike.
A success is 200 and one key:
{
"data": {}
}
data is the whole profile as it stands after the write, not an echo
of the request, and it is the same document for every endpoint — including the
ones that changed a single field.
A failure carries one key as well:
{
"errors": [
{
"code": 1786495903,
"message": "Request token missing or invalid."
}
]
}
code is a TYPO3 style exception code identifying the one line that
refused the request. message is written for a developer, is not
localized, and never repeats a value the request carried.
A 422 uses the same key with one entry per rejected value, and those
entries carry a field:
{
"errors": [
{
"field": "shortname",
"code": 1221560718,
"message": "Enter a short name."
}
]
}
field is the name of the rejected field, or null for an error
that belongs to the record rather than to one of its fields. A rejected image
upload is keyed under image. The message of a validation error
is localized: it comes from the label file and is already translated and
substituted.
The profile document
The object under data, and the same document the edit plugin renders
into its markup:
| Key | Value |
|---|---|
uid | The profile uid. |
shortname, firstname, lastname,
bio | Strings. |
birthday | YYYY-, or "" for "no birthday". |
hidden | Boolean. Readable, and writable by no endpoint. |
image | null, or an object with uid (the
sys_ uid), file (the
sys_ uid), public, name,
extension, mime, size, title,
alternative, width and height. |
addresses | A list of objects with uid, type, line1,
line2 and hidden, in their stored order. |
emails | A list of objects with uid, type, email and
hidden, in their stored order. |
Both collections contain the records the owner has hidden, marked by their
hidden flag. That is what lets an owner find and publish them again.
Status codes
| Status | Meaning |
|---|---|
200 | The write was performed. The body carries the resulting document. |
400 | The request is malformed: a wrong media type, a body that is not a JSON object, a missing or wrongly typed key, an unknown child collection, an unknown field name, or more than one uploaded file. |
403 | The request token is missing or invalid, or no website user is logged in. Which of the two is not distinguished. |
404 | The addressed record does not exist, or does not belong to the calling session. The two are deliberately indistinguishable. |
405 | The request method is not POST. The response carries
Allow: POST. |
409 | A workspace is active. The request is well formed and authorised, and the state of the session is what makes it unanswerable. |
422 | A submitted value was rejected by a validation rule. The body names the field. |