Appearance
API Entry
Compatibility
- ExpressionEngine 7.3+ (v10). For older versions of ExpressionEngine see the installation page.
Installation
The Entry API comes natively with the Webservice module.
Errors
An error has "success": false with the message, and an HTTP status:
| Status | When |
|---|---|
400 | The request is not complete, e.g. no entry_id or search, no search param, a search on an unknown field, a search that could not be executed, a search that returns more than one entry without force, or an update with a search without a filter that selects entries |
403 | No permission for the channel, the entry or the author (member_id) |
404 | The channel or the entry is not found, also when a search finds nothing |
422 | A field is missing or invalid, e.g. a required field, an invalid date or an error of a fieldtype |
The errors of the module itself (credentials, permissions of the API, rate limiting) are on the default parameters and rate limiting pages.
Methods
POST /index.php/webservice/entry — Create Entry
Create a new entry in a channel.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
channel_name | string | Required | The name of the channel where the entry must be inserted |
title | string | Required | The title for the entry |
url_title | string | Optional | The url title, default made from the title |
status | string | Optional | The status of the entry, default the default status of the channel |
allow_comments | boolean | Optional | Allow comments? Default the setting of the channel |
sticky | boolean | Optional | Is the entry sticky? |
entry_date, expiration_date, comment_expiration_date | integer / string | Optional | A Unix timestamp or a date, the entry_date defaults to now |
custom_fields | mixed | Optional | Any custom fields can be added, by their short name |
structure, seo_lite, publisher_lang_id, publisher_status | mixed | Optional | The data of these add-ons, see Compatible add-ons |
category | array / string | Optional | The category IDs, an array or a pipe or comma separated string: 1|2|3. Only the categories of the category groups of the channel. Replaces the default category of the channel |
member_id | integer | Optional | Member ID, otherwise defaults to the logged-in user |
site_id | integer | Optional | Default 1, required when Strict site_id is on in the settings |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Request Example
php
$client = new \GuzzleHttp\Client();
$request = new \GuzzleHttp\Psr7\Request(
'POST',
'https://your-domain.com/index.php/webservice/entry',
['Content-Type' => 'application/json'],
json_encode([
'auth' => [
'session_id' => 'fa78db6b673b83c211e8cc7153528e9087f62de0',
],
'data' => [
'channel_name' => 'news_channel',
'title' => 'Test entry',
'status' => 'closed',
'sticky' => 'y',
'allow_comments' => 'n',
'news_body' => 'This is a full body text',
'news_extended' => 'This is some small text',
'category' => '1|2|3|4',
'custom_file_field' => [
'filedata' => base64_encode(file_get_contents('path_to_image.jpg')),
'filename' => 'filename.jpg',
'dir_id' => 6,
],
],
])
);
$response = $client->send($request, ['timeout' => 2]);Response — 201 Created
json
{
"success": true,
"message": "Created successfully",
"metadata": {
"id": 42
}
}Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | true when the entry was created successfully |
message | string | Human readable status message |
metadata.id | integer | The ID of the newly created entry |
GET /index.php/webservice/entry — Search / Read Entry
Search or retrieve entries from a channel.
Note: the order of the request parameters will be the order of the query.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entry_id | integer | Optional | Fetch a single entry by ID |
channel (or channel_name) | string | Optional | Channel name, a contains search: use =news for an exact match |
channel_id | integer | Optional | Search by Channel ID |
url_title | string | Optional | URL title, a contains search: use =my-title for an exact match |
status | string | Optional | Filter by entry status. On a public endpoint a guest only gets open entries, a member also the other statuses of its assigned channels |
limit | integer | Optional | Maximum number of results (used with offset), default 99 |
offset | integer | Optional | Result offset (used with limit) |
orderby | string | Optional | Sort results by this column or custom field, default entry_date |
sort | string | Optional | Sort direction (asc or desc), default desc |
start_on | string | Optional | Filter entries from this date |
stop_before | string | Optional | Filter entries before this date |
show_expired | boolean | Optional | true also returns the expired entries, false hides them. Default the setting Show expired entries in the settings |
search_all | string | Optional | Search in the title and all custom fields. Every word must be in one field and words shorter than 3 characters are skipped; without any other word the search is ignored. "two words" searches for the phrase, -word excludes a word. Combined with AND with the other parameters |
grid_field[col_name] / matrix_field[cell_name] | string | Optional | Search in a grid/matrix column, see Search in Grid/Matrix cells |
cat:[field_name] | string | Optional | Search on category fields (cat_id, group_id, cat_name, cat_url_title, or a custom field) |
include | string | Optional | Include extra data, pipe separated: channel|categories|author|parents|structure, see below |
return | string | Optional | Return only the metadata, with an empty data: data[return]=meta |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Request Example
GET /index.php/webservice/entry?auth[session_id]=fa78db6b673b83c211e8cc7153528e9087f62de0&data[site_id]=1&data[entry_id]=43php
$client = new \GuzzleHttp\Client();
$response = $client->get('https://your-domain.com/index.php/webservice/entry', [
'query' => [
'auth' => [
'session_id' => 'fa78db6b673b83c211e8cc7153528e9087f62de0',
],
'data' => [
'site_id' => 1,
'entry_id' => 43,
],
],
'timeout' => 2,
]);Response — 200 OK
json
{
"success": true,
"message": "Successfully read",
"metadata": {
"id": "43",
"limit": 99,
"offset": 0,
"total_results": 1,
"absolute_results": 1
},
"data": [
{
"entry_id": 43,
"title": "My Entry",
"status": "open",
"channel_id": 1
}
]
}When nothing is found, the response is a 404 with {"success": false, "message": "No Entry found"}.
Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | true when the request succeeded |
message | string | Human readable status message |
metadata.id | string | The IDs of the returned entries, pipe separated |
metadata.limit, metadata.offset | integer | The paging of the request |
metadata.total_results | integer | The number of returned entries |
metadata.absolute_results | integer | The number of matching entries, without the limit |
data[] | array | Array of matching entry objects |
data[].entry_id | integer | The entry ID |
data[].title | string | The entry title |
data[].status | string | The entry status |
The ip_address is only returned to a member with the permission Access members. For everyone else a search or orderby on it is ignored. Every entry has all its columns and fields, also when the value is empty (0, "" or false), e.g. view_count_one: 0 or sticky: false. Before v10.0.0 empty values were left out (Breaking change).
Include
| Include | Description |
|---|---|
channel | The channel of the entry |
categories (or category) | The categories of the entry |
author (or member) | The member_id, username and screen_name of the author, returned as both author and member. The email only for the author itself or a member with the permission Access members |
parents | The entries that relate to this entry with a relationship field (also in a grid): entry_id, title, url_title, channel_id, status and the field_id. Only the entries of the channels the member has access to, without closed entries. Available from v10.0.0, before it was the Parent Relationship extension |
structure | The Structure page of the entry: type (page or listing), uri, url, parent_id, parent_uri, template_id, hidden and listing_channel_id, or null when the entry has no page. Needs the Structure add-on. Available from v10.0.0, before it was the Structure extension |
bash
curl -G 'https://your-domain.com/index.php/webservice/entry' \
-H 'Authorization: Bearer <shortkey>' \
--data-urlencode 'data[entry_id]=12' \
--data-urlencode 'data[include]=parents|structure'Search Mechanism
Basic Operators
| Type | Operator | Example |
|---|---|---|
| Contains | (empty) | data[body]=test |
| Contains with wildcard | (empty) | data[body]=~test → results in %test% |
| Contains NOT | (empty) | data[body]=not test |
| Exact matching | = | data[body]==test |
| Exact NOT matching | not = | data[body]=not =test |
| Numeric matching | < > <= >= | data[body]=>10 |
| Empty field | IS_EMPTY | data[body]=IS_EMPTY |
| Not empty field | IS_EMPTY | data[body]=not IS_EMPTY |
A field with _id in its name is an exact match without an operator. A value with , or | is always split into multiple values.
An entry without a value for the field, e.g. of a channel without that field, counts as empty: it matches not test, not =test and IS_EMPTY. Combine them with channel to search in one channel.
OR / AND
By default the query is built with AND. To switch to OR, add or before the value:
data[title]=or johnMultiple Values
Use a pipe | or comma , to match multiple values (results in an OR query). The values are a group, combined with AND with the other fields:
data[title]=test|johnsql
title LIKE '%test%' OR title LIKE '%john%'Use =test|=john for an exact match.
Groups
Group conditions like you would in SQL: (field LIKE '%4%' OR field LIKE '%5%') AND (field LIKE '%2%' OR field LIKE '%3%')
The group name can be anything, but must be unique per group.
data[group_one][group_and][][title]=test|john&data[group_two][group_and][][title]=willem|doesql
(title LIKE '%test%' OR title LIKE '%john%') AND (title LIKE '%willem%' OR title LIKE '%doe%')Use group_or instead of group_and to combine a group with OR with the groups before it.
PUT /index.php/webservice/entry — Update Entry
Update an existing entry by entry ID or search parameters.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entry_id | integer | Required* | The entry ID to update |
search[] | array | Required* | Identify the entry via search (see Search Mechanism) |
title | string | Optional | The title for the entry |
url_title | string | Optional | The url title |
status | string | Optional | The status of the entry |
allow_comments | boolean | Optional | Allow comments? |
sticky | boolean | Optional | Is the entry sticky? |
entry_date, edit_date | integer / string | Optional | A Unix timestamp or a date. Without an edit_date it is set to now |
expiration_date, comment_expiration_date | integer / string | Optional | A Unix timestamp or a date. When not given, the date stays the same; an empty value or 0 clears it |
custom_fields | mixed | Optional | Any custom field(s). A field that is not given stays the same, a required field cannot be emptied (422) |
category | array / string | Optional | The category IDs, an array or a pipe or comma separated string: 1|2|3. Only the categories of the category groups of the channel. Replaces the categories of the entry, empty removes them |
member_id | integer | Optional | The author, otherwise the author stays the same |
*Either
entry_idorsearch[]is required.
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Request Example — by Entry ID
PUT /index.php/webservice/entry?auth[session_id]=fa78db6b673b83c211e8cc7153528e9087f62de0&data[site_id]=1&data[entry_id]=3988&data[title]=updated&data[status]=openphp
$client = new \GuzzleHttp\Client();
$request = new \GuzzleHttp\Psr7\Request(
'PUT',
'https://your-domain.com/index.php/webservice/entry',
['Content-Type' => 'application/json'],
json_encode([
'auth' => [
'session_id' => 'fa78db6b673b83c211e8cc7153528e9087f62de0',
],
'data' => [
'site_id' => 1,
'entry_id' => 3988,
'title' => 'Updated title',
'status' => 'open',
'sticky' => 'y',
'allow_comments' => 'n',
'news_body' => 'This is a full body text',
'news_extended' => 'This is some small text',
],
])
);
$response = $client->send($request, ['timeout' => 2]);💻 Request Example — by Search
⚠️ The webservice returns an error if multiple entries match. Use
forceto update the first match. Updating byentry_idis safer but requires an extra request to look up the ID first. The search needs a filter that selects entries, otherwise the update returns a400, also withforce. Parameters likelimit,orderbyandshow_expired, asearch_allwithout a usable word and values like,or~do not count.
php
$request = new \GuzzleHttp\Psr7\Request(
'PUT',
'https://your-domain.com/index.php/webservice/entry',
['Content-Type' => 'application/json'],
json_encode([
'auth' => [
'session_id' => 'fa78db6b673b83c211e8cc7153528e9087f62de0',
],
'data' => [
'search' => [
'custom_field' => '=test', // = sign is for explicit matching
'force' => true, // update first match if multiple found
],
'title' => 'Updated title',
'status' => 'Closed',
],
])
);Response — 200 OK
json
{
"success": true,
"message": "Successfully updated",
"metadata": {
"id": 3988
}
}📤 Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | true when the entry was updated successfully |
message | string | Human readable status message |
metadata.id | integer | The ID of the updated entry |
DELETE /index.php/webservice/entry — Delete Entry
Delete an entry by its entry ID.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entry_id | integer | Required | The entry ID to delete |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Request Example
DELETE /index.php/webservice/entry?auth[username]=person@gmail.com&auth[password]=1232&data[site_id]=1&data[entry_id]=3983php
$client = new \GuzzleHttp\Client();
$request = new \GuzzleHttp\Psr7\Request(
'DELETE',
'https://your-domain.com/index.php/webservice/entry',
['Content-Type' => 'application/json'],
json_encode([
'auth' => [
'username' => 'person@gmail.com',
'password' => '1232',
],
'data' => [
'site_id' => 1,
'entry_id' => 3983,
],
])
);
$response = $client->send($request, ['timeout' => 2]);Response — 200 OK
json
{
"success": true,
"message": "Successfully deleted",
"metadata": {
"id": 3983
}
}Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | true when the entry was deleted successfully |
message | string | Human readable status message |
metadata.id | integer | The ID of the deleted entry |
POST /index.php/webservice/entry/view_count — Count a view
Available from v10.0.0, before it was the separate View Count API
Adds one view to a view count of an entry, like track_views of the channel module. For sites that show their entries without the templates of ExpressionEngine (e.g. a headless front-end). The counts are read with the view_count_one to view_count_four of an entry.
| Parameter | Type | Required | Description |
|---|---|---|---|
entry_id | integer | Required | The entry |
counter | integer | Optional | 1 (default) to 4: the view_count_one to view_count_four column |
- Enable entry view tracking must be on in the channel settings of ExpressionEngine, otherwise a
403is returned. - Only an entry that can be seen: not closed, not in the future and not expired, otherwise a
404. - The count can only go up by one per call, to count the views of visitors make the endpoint public and use the rate limiting.
json
{"success": true, "message": "View counted", "metadata": {"id": 12}, "data": [{"entry_id": 12, "view_count_one": 43}]}The hook webservice_increment_view_count_end is called with the entry_id and the counter.
Hooks
Every hook is called as webservice_<name> and as reinos_webservice_<name>, e.g. webservice_create_entry_end and reinos_webservice_create_entry_end.
webservice_create_entry_start
(added in v3.2.1)
$entry_data = ee()->extensions->call('webservice_create_entry_start', $entry_data);webservice_create_entry_end
(added in v2.2)
ee()->extensions->call('webservice_create_entry_end', $entry_id, $post_data);webservice_read_entry_start
(added in v3.2.1)
$entry_data = ee()->extensions->call('webservice_read_entry_start', $entry_data);webservice_create_entry
(added in v4.4.2)
Runs after the validation, with the entry model before it is saved
$entry = ee()->extensions->call('webservice_create_entry', $entry, $post_data);webservice_read_entry_end
(added in v2.2)
$entry_data = ee()->extensions->call('webservice_read_entry_end', $entry_data, $post_data);webservice_update_entry_start
(added in v3.2.1)
$entry_data = ee()->extensions->call('webservice_update_entry_start', $entry_data);webservice_update_entry
(added in v4.4.2)
Runs after the validation, with the entry model before it is saved
$entry = ee()->extensions->call('webservice_update_entry', $entry, $post_data);webservice_update_entry_end
(added in v2.2)
With the saved entry model
ee()->extensions->call('webservice_update_entry_end', $entry, $post_data);webservice_delete_entry_start
(added in v3.2.1)
$entry_data = ee()->extensions->call('webservice_delete_entry_start', $entry_data);webservice_delete_entry_end
(added in v2.2)
ee()->extensions->call('webservice_delete_entry_end', $entry_id);webservice_modify_search
(added in v4.0)
With this hook you can extend the search by your own. Just make sure the hook will return an array of entry_ids.
$entry_ids = ee()->extensions->call('webservice_modify_search', $values, $fields);webservice_search_entry_per_entry
(added in v3.2)
$entry_data = ee()->extensions->call('webservice_search_entry_per_entry', $entry_data, $fields, $post_data);webservice_read_entry_per_entry
(added in v3.2)
Called per entry, with the same data as webservice_search_entry_per_entry
$entry_data = ee()->extensions->call('webservice_read_entry_per_entry', $entry_data, $fields, $post_data);webservice_search_entry_start
(added in v3.2.1)
$entry_data = ee()->extensions->call('webservice_search_entry_start', $entry_data);webservice_search_entry_end
(added in v2.2)
$entry_data = ee()->extensions->call('webservice_search_entry_end', $entry_data, $post_data);webservice_entry_row
(added in v3.5)
Will be called per entry when reading entries, after webservice_read_entry_per_entry
$entry_data = ee()->extensions->call('webservice_entry_row', $entry_data, $fields, $post_data);webservice_post_entry_row
(added in v6.1.0)
Called per entry with the entry model, before the fields are processed
$entry = ee()->extensions->call('webservice_post_entry_row', $entry, $fields, $post_data);webservice_read_entry_matrix
(added in v4.0)
$value = ee()->extensions->call('webservice_read_entry_matrix', $value, $col_settings, $row_id);webservice_read_entry_grid
(added in v4.0)
$value = ee()->extensions->call('webservice_read_entry_grid', $value, $col_settings, $row_id);