ezprestapi: a REST content provider for remote updates
This page is for developers who publish content into Exponential from scripts and other services. ezprestapi is a
REST API provider (versions 1 and 2 of the ezp API) for updating content remotely: other systems list, read, create, update and
delete nodes over HTTP with an OAuth token. It is a registered provider of the kernel's REST framework.
Since 1.2.6 it serves REST API v1 and v2 as one extension: one activation (ActiveExtensions[]=ezprestapi), one
provider class for the prefixes ezp and ezpl, one route table, one OAuth2 setup. It replaces the deprecated
ezprestapiprovider. The guide REST API v1 and v2: one extension
has the routes, authentication, configuration and the migration.
How it is wired
The provider classes are named in rest.ini:
[ApiProvider]
ProviderClass[ezp]=ezp7xRestApiProvider
ProviderClass[ezpl]=ezp7xRestApiProviderThe routes are served below the REST prefix of the installation and carry a version. Reads answer at v1 and v2,
writes at v2 only. With the prefixes ezp and ezpl they answer, for example, at
/api/ezp/v1/content/node/<id>/list/offset/0/limit/20 and /api/ezpl/v2/content/node/create.
ProviderClass[ezp] of this extension replaces the one of ezprestapiprovider, which served
v1. Before 1.2.5 that took v1 away (every /api/ezp/v1/... answered 404) although existing clients, a mobile app
among them, read through it; since 1.2.5 this provider serves the v1 reads itself. That needs the kernel of
Exponential 6.0.15, whose versioned routes take a list of versions; on an older kernel the routes are v2 only, as
before. Since 1.2.6 ezprestapiprovider (6.0.4) sets the same provider class, so every combination of the two
extensions routes every content URL to ezp7xRestContentController.
The template of OutputFormat=xhtml is set for this extension's controller (1.2.6):
[ezp7xRestContentController_viewContent_OutputSettings]
Template=rest_pagelayout.tplSince 1.2.9 the create, update and delete calls are written by the kernel's expContentWriter
(kernel/classes/contentwriter/expcontentwriter.php, Exponential 6.0.15); the extension no longer requires
nxc_powercontent. An update publishes a new version of the node's object with the values sent
(Writing content). On a kernel older than 6.0.15 the write calls answer 501 not_implemented.
Authentication is the REST
framework's OAuth2: obtain a token at POST /api/auth/v1/oauth/token (grants authorization_code and
refresh_token) and send it in the Authorization: Bearer (or OAuth) header; the oauth_token parameter is read
from a POST form body, and from the query string only with rest.ini [OAuthSettings] AllowTokenInQuery=enabled
(OAuth token hardening).
A user can also make a personal API key on the site and send it as
Authorization: Bearer expk_...; the create, update and delete routes then need the key's publish, edit or
remove scope as well (rest.ini [ApiKeySettings] RouteScopes[], RouteGuards[]).
Permissions
Since 1.2.5 the controller checks the current user's policies before every call, whatever the request was
authenticated with, the way the content module checks them: content/read for reads (a list or count also of its
parent node), content/create of the class below parentNodeID in languageLocale (with every limitation),
content/edit for an update (in its language), content/remove of every location of the object and everything below them for a
removal. A refusal answers 403 {"error":"access_denied","error_message":...} and writes nothing. Before 1.2.5 the
writes asked nothing: an OAuth token could create and remove where its user had no right to. The check is the kernel's
expRestContentPermission (Exponential 6.0.15); on an older kernel the extension refuses every write with 403
rather than run it unchecked. The details are in the guide:
Permissions of the REST interface.
| Answer | When |
|---|---|
201 {"message":"Created","objectId","nodeId"} | a create was published |
200 {"message":"Updated","objectId","nodeId","version","languageLocale"} | an update was published as a new version |
202 {"message":"Waiting for a workflow",...} | a workflow holds the publication of an update |
200 {"message":"Removed","nodeId","objectId"} | a removal is done |
400 invalid_request | parentNodeID, classIdentifier or languageLocale is missing, or names a class or language that does not exist; an update without values; an unknown attribute, a value that is not text, or a file value that is not a public http(s) URL |
403 access_denied | the user may not do it |
404 not_found | the node does not exist |
501 not_implemented | a write on a kernel older than 6.0.15 (no expContentWriter) |
401 | no or an invalid token or key (a POST or DELETE too; before 6.0.15 they answered 405) |
Writing content
POST /content/node/<id> and PUT on the same URL (or POST with _method=PUT) publish a new version of the node's
object; attributes left out keep their values. languageLocale is optional (default: the object's main language); a
language the object does not have yet adds that translation. The new version goes through the content/publish
operation, so workflows run. A personal API key needs the edit scope
(RouteScopes[ezp7xRestContentController_UpdateContentNode]=edit).
Create and update accept the values as form fields or as a JSON object (Content-Type: application/json), by
attribute identifier and as text: rich text (ezxmltext) as HTML, converted with the Online Editor's input parser
(links and embeds become relations); files (ezimage, ezbinaryfile, ezmedia) as <http(s) URL> or
<URL>|<alternative text or file name>, downloaded from a public address only (no server path, file://, other
scheme, credentials in the URL, or address in a private, loopback, link-local or reserved network; no redirects; at
most 50 MB); every other datatype in its text form (fromString()). Before 1.2.9 a create read a file value from a
local server path or downloaded any URL through nxc_powercontent. The guide has the details and a curl example:
Updating and the values of a write.
Routes
Registered by ezp7xRestApiProvider (classes/rest_provider.php); all but the Atom feed run in
ezp7xRestContentController, so they all get its checks:
| Route | Versions | Purpose |
|---|---|---|
GET /content/node/:nodeId | v1, v2 | One node |
GET /content/node/:nodeId/list... (a regular expression route) | v1, v2 | List children (offset, limit, sort) |
GET /content/node/:nodeId/fields, .../field/:fieldIdentifier, .../childrenCount | v1, v2 | Fields and counts |
GET /content/object/:objectId, .../fields, .../field/:fieldIdentifier | v1, v2 | The same by object id |
GET /content/node/:nodeId/listAtom (ezpRestAtomController) | v1, v2 | Atom feed (answers 500: known issue) |
POST /content/node/create | v2 | Create and publish content |
DELETE or POST /content/node/delete/:nodeId | v2 | Remove the node's object |
POST or PUT /content/node/:nodeId | v2 | Update: publish a new version with the values sent |
| Before 1.2.5 and 6.0.15 | Since |
|---|---|
/api/ezp/v1/...: 404 for every route | the reads answer at v1 |
POST /api/ezp/v2/content/node/create without or with a refused token: 405 | 401 or 403 |
DELETE /api/ezp/v2/content/node/delete/<id>: 405 | runs (and POST) |
| a delete that worked answered 500 with "has been removed" | 200 with the ids |
fields and object reads ran in ezpRestContentController of ezprestapiprovider | in this extension's controller |
Call it from the command line
bin/php/ezrestcall.php (1.2.0) is an example client built on Guzzle. Guzzle is not part of the distribution: run
composer require guzzlehttp/guzzle in the installation that runs the script. It speaks HTTPS to a host:
# list the children of a node (the script appends /limit/<n> and the token)
php bin/php/ezrestcall.php --host=www.example.com --token=<oauth token> --action=list --uri=/api/ezpl/v2/content/node/2/list| Option | Meaning |
|---|---|
--host | Host name without protocol |
--token | An OAuth token |
--action | list (the default), login or create |
--uri | The path to call |
The login and create actions are examples to read and adapt. create posts a story to
/api/ezpl/v2/content/node/create with parentNodeID, classIdentifier, languageLocale and the attribute values.
The values in them belong to the author's own site, so copy the script and replace them before use. Do not keep real
credentials or tokens in a script, or in shell history on shared machines.
What changed
| Version | Date | Change |
|---|---|---|
| 1.2.0 | 29 October 2024 | Initial import of the solution's dependencies, funding information and the example client. |
| 1.2.1 to 1.2.4 | 27 September to 2 October 2026 | Version, license and website; description; 7x and Exponential Foundation headers on the entry point files; commands and cronjob parts are classes the files call and start through the shared helpers. |
| 1.2.5 | 6 October 2026 | Every read and write checks the current user's policies for every kind of authentication; reads answer at v1 again; delete takes DELETE; create and delete answer 201 and 200 with the ids. Needs Exponential 6.0.15 for the checks and v1. |
| 1.2.6 | 15 October 2026 | OutputFormat=xhtml renders again ([ezp7xRestContentController_viewContent_OutputSettings] Template=rest_pagelayout.tpl; it answered an empty renderedOutput); name, description and README say the extension serves REST API v1 and v2 and replaces ezprestapiprovider; the model tests moved in from ezprestapiprovider. |
| 1.2.9 | 12 November 2026 | REST v2 updates content (POST or PUT /content/node/<id> publishes a new version); create and update accept a JSON body; create, update and delete are written by the kernel's expContentWriter, so nxc_powercontent is no longer required; file values are downloaded from public http(s) addresses only. Needs Exponential 6.0.15 for the writes. |
ezprestapiprovider is deprecated; its final release, 6.0.4, is a compatibility shim that extends this extension's provider and controller. See the migration.
Related pages
- REST API v1 and v2: one extension
- nxc_powercontent, ezprestapiprovider (deprecated)
- Remote services (expservices)
- Personal API keys: REST requests with a key of the user
- CLI, cronjob and view abstractions
- Chronicle and release notes
- Change ledger
- Behaviour changes of the extensions
- Months: 2024-10, 2026-10 (all extensions)
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.