WordPress plugin that exposes protected REST API endpoints for EventON ajde_events, including pagination, search, create, update, delete, and RSVP delta-sync operations for administrator-authenticated integrations.
- Quick start
- Features
- Requirements
- Installation
- Packaging
- Development quality gates
- Releases
- Authentication
- Privacy
- MCP compatibility
- MCP schema manifest
- WordPress abilities
- API reference
- Developer hooks
- Automatic updates
- Related packages
- License
- Copy this plugin into your WordPress plugins directory.
- Activate EventON APIfy.
- Go to Settings -> EventON APIfy.
- Enable Event API and the capabilities you want to expose.
- Create a WordPress Application Password for an administrator user.
- Call the endpoint:
curl -u your_username:your_app_password \
"https://your-site.com/wp-json/eventonapify/v1/events?per_page=10&page=1"See GitHub Releases for the changelog and the latest packaged build.
- Dedicated namespace:
eventonapify/v1 - List EventON events with pagination, search, status filtering, and exact slug lookup
- Fetch a single event by ID
- Create new
ajde_eventsposts via REST - Update existing events, including EventON timestamps, status, virtual, repeat, RSVP, and taxonomy-backed location/organizer metadata
- Trash events through the API
- Read yes-only RSVP attendance summaries for EventON RSVP events
- List RSVP attendee records and additional RSVP form fields for EventON RSVP events, including delta-sync reads by checkpoint
- Administrator-only access
- Global Event API switch plus per-capability toggles for event reads/writes and RSVP reads
- Optional
wp/v2compatibility mode for generic WordPress tools such asmcp-wp - Read-only WordPress abilities for abilities-aware clients such as MCP adapters
- Read-only MCP schema manifest for clients that need an executable EventON content contract
- Compatible with WordPress Application Passwords
- Git Updater metadata included for dashboard-based GitHub updates
- WordPress
7.1+ - PHP
8.0+ - EventON installed and active
- HTTPS-enabled site recommended for secure API authentication
- Install the packaged zip from GitHub Releases, or upload the plugin folder to
/wp-content/plugins/eventon-apify/when working from source in local development. - Activate it from Plugins in wp-admin.
- Open Settings -> EventON APIfy.
- Enable Event API.
- Enable the capabilities you want available to administrators.
- If you are using a generic WordPress client such as
mcp-wp, also enable WP v2 compatibility. - If you installed from GitHub and want in-dashboard updates, install Git Updater.
Upgrade note: from 1.3.2 onward, the plugin keeps a backup copy of the API and WP v2 compatibility settings so future upgrades can restore them if those options go missing during an update.
Build an installable plugin zip from the repo root:
./build.shThat creates a file like eventon-apify-x.y.z.zip in the project root, ready to upload in Plugins -> Add New -> Upload Plugin.
Composer dependencies are development-only and are excluded from release packages. Install and run the complete local gate with:
composer install
composer qualityThe individual commands are:
composer lint— WordPress security, deprecated API, alternative-function, and global-prefix PHPCS rules.composer analyse— PHPStan analysis against WordPress stubs.composer test— the dependency-free PHP unit suite.composer performance— the deterministic 10,000-record RSVP helper budget.XDEBUG_MODE=coverage composer coverage— unit coverage report and minimum line-coverage gate; writescoverage.json.
CI runs the quality gate with Xdebug, uploads the coverage report, and separately exercises the supported PHP matrix and WordPress integration smoke test.
Prepare matching version metadata and release-notes/X.Y.Z.md in a PR into
main. Apply the release label before merging. GitHub runs the exact
merge commit through the existing CI gates and publishes its tested ZIP and
checksum automatically. See AGENTS.md for the complete procedure and retries.
- Add RSVP attendee delta sync with canonical
updated_attimestamps.
- Improve API docs and request examples for event and RSVP routes.
- Reject invalid
updated_aftervalues with a400validation error.
## Authentication
This API requires WordPress authentication and checks for the `manage_options` capability.
Route access is controlled in two layers:
- Global switch: **Event API**
- Per-capability switches: **List events**, **Read single event**, **Create events**, **Update events**, **Delete events**, **Read RSVP summary**, **List RSVP attendees**
Recommended method: **Application Passwords**.
1. Go to **Users -> Profile**.
2. Create a new application password.
3. Use `username:application_password` in Basic Auth.
Example:
```bash
curl -u your_username:your_app_password \
"https://your-site.com/wp-json/eventonapify/v1/events?search=ride&status=publish,draft"
Single-event example:
curl -u your_username:your_app_password \
"https://your-site.com/wp-json/eventonapify/v1/events/123"EventON APIfy works with EventON data stored in standard WordPress/EventON post meta and taxonomy meta rather than creating its own custom tables.
Depending on the enabled routes and submitted payloads, that data can include:
- organizer contact details
- location contact details
- virtual event credentials and visibility settings
- RSVP notification email recipients
Both the custom namespace and the optional wp/v2 compatibility mode are intended for administrator-authenticated access only. Site owners remain responsible for their privacy disclosures, retention policies, and any export/erasure workflows required for EventON-managed content.
If you use InstaWP mcp-wp or another WordPress MCP server, use ajde_events as the content type and fetch the EventON APIfy MCP manifest first when the client supports plugin-published contracts.
The manifest now advertises the custom EventON APIfy events route as the preferred content endpoint for EventON event reads and writes:
- Content type:
ajde_events - Preferred content endpoint:
/wp-json/eventonapify/v1/events
If your client specifically requires the standard WordPress namespace, enable WP v2 compatibility in Settings -> EventON APIfy. That additionally exposes EventON through the standard WordPress REST API:
- Content type:
ajde_events - Content endpoint:
/wp-json/wp/v2/ajde_events - Taxonomies:
event_type,event_location,event_organizer
Recommended mcp-wp usage:
- Use
content_type: "ajde_events"for content operations - Send EventON-specific fields either at the top level or inside
custom_fields/fields, includingfeatured_media,start_date,start_time,end_date,end_time,timezone,event_status,location,organizers,flags,virtual,repeat, andrsvp - Use the MCP taxonomy tools for
event_type,event_location, andevent_organizerwhen you want direct taxonomy-level operations - Fetch the EventON APIfy MCP manifest first if your MCP server supports plugin-published content contracts
Important: the EventON-specific compatibility fields are available to administrator-authenticated requests only, and their responses redact sensitive fields such as virtual access secrets and notification email metadata. Routes that compatibility mode itself adds to wp/v2 are also administrator-only. EventON 5 already registers ajde_events and its event_type taxonomies on wp/v2; those routes keep the access EventON and WordPress core give them (block editor, public reads of published events), and compatibility mode only adds the admin-only fields on top.
EventON APIfy registers three read-only WordPress abilities in the eventon-apify category, for clients that discover and run abilities, such as an MCP adapter or the WordPress AI client:
| Ability | Input | Returns |
|---|---|---|
eventon-apify/get-status |
none | Plugin version, whether EventON and EventON RSVP are active, whether the API and wp/v2 compatibility are enabled, and each operation toggle. Works while the API is disabled; returns no event data. |
eventon-apify/search-events |
search, page, per_page (1-100), status (list), starts_on_or_after, starts_before, upcoming, order, orderby |
total, pages, page, per_page, and a summary per event: id, title, slug, status, link, start_at, end_at, timezone, event_status, attendance_mode, location_name, event_type. |
eventon-apify/get-event |
id |
One event in the eventonapify/v1 shape, with contact details, virtual access secrets, and notification emails removed (the same redaction as the wp/v2 fields). |
- Same rules as the REST API. Each ability runs the matching
eventonapify/v1route in-process, so the Event API switch, theList events/Read single eventtoggles, argument handling, and error codes are identical. A disabled toggle returns the sameeventon_apify_capability_disablederror. - Administrator-only, including discovery. Execution requires
manage_options. WordPress lets any logged-in user list REST-exposed abilities, so EventON APIfy hides its own from non-administrators inwp_get_abilities()and refuses its routes under/wp-json/wp-abilities/v1/abilities/eventon-apify/for them, matching the administrator-only MCP schema manifest. - Exposed to clients. The abilities set the WordPress 7.1
publicflag, so core lists them at/wp-json/wp-abilities/v1/abilitiesand runs them at.../abilities/<name>/run(GET, withinputas a query parameter, because they are read-only). Core prepares their schemas for clients withwp_prepare_json_schema_for_client(). - Inputs are validated by WordPress. Unknown keys,
per_pageabove 100, and statuses outsidepublish,draft,private,pending,futureare rejected withability_invalid_inputbefore anything runs. - There are no write abilities. Creating, updating, and deleting events stays on the REST API.
EventON APIfy also publishes a read-only discovery contract for compatible MCP servers:
- Manifest:
/wp-json/eventonapify/v1/mcp-schema - Content type detail:
/wp-json/eventonapify/v1/mcp-schema/ajde_events - Content type detail:
/wp-json/eventonapify/v1/mcp-schema/event_rsvpswhen the RSVP addon is active
The manifest describes:
preferred_endpoint: "eventonapify/v1/events", the transactional route for reads and writessupported_operations: ["list", "get", "create", "update", "delete"]forajde_events;event_rsvpsis["list"]onlyrelated_endpoints: the single-event routeeventonapify/v1/events/{id}and, as a secondary route only,wp/v2/ajde_eventsfor WP v2 compatibility modepreferred_write_mode: "fields"for structured client input- normalized EventON
fieldswith nested object and array shapes, includinglocation.lat/location.lontyped["string", "number"]and everyrepeat.intervalsitem key the write accepts (start_at,end_at,start_timestamp,end_timestamp,start_date,start_time,end_date,end_time) - executable
validation_rulesplus additional runtimevalidation_notes. Create requirestitle(required_for_create: ["title"]) and at least one ofstart_dateorstart_at(one_of_required_for_create: [["start_date", "start_at"]], a list of groups where each group needs at least one field) examples.createandexamples.updatepayloads for MCP clients- coarse runtime availability such as whether EventON is active, the custom API is enabled, and
WP v2 compatibilityis on - when the RSVP addon is active, a read-only
event_rsvpscontent type for/wp-json/eventonapify/v1/events/{event_id}/rsvps - when the RSVP addon is active, the related RSVP summary endpoint
/wp-json/eventonapify/v1/events/{event_id}/rsvps/summary
The manifest routes require an authenticated administrator, matching the event API authorization policy. Use a WordPress Application Password for automation clients.
Important: the manifest is discovery-only. Compatible clients should follow the advertised preferred_endpoint, which for ajde_events is /wp-json/eventonapify/v1/events. The contract examples are client-facing normalized payloads, not raw WordPress REST requests.
When using wp/v2, clients may send those normalized EventON fields directly on the request body or nest them inside fields / custom_fields.
Example:
curl -u your_username:your_application_password \
"https://your-site.com/wp-json/eventonapify/v1/mcp-schema/ajde_events"GET /wp-json/eventonapify/v1/mcp-schemaGET /wp-json/eventonapify/v1/mcp-schema/ajde_eventsGET /wp-json/eventonapify/v1/mcp-schema/event_rsvpswhen the RSVP addon is activeGET /wp-json/eventonapify/v1/eventsGET /wp-json/eventonapify/v1/events/<id>GET /wp-json/eventonapify/v1/events/<id>/rsvps/summaryGET /wp-json/eventonapify/v1/events/<id>/rsvpsPOST /wp-json/eventonapify/v1/eventsPUT /wp-json/eventonapify/v1/events/<id>PATCH /wp-json/eventonapify/v1/events/<id>DELETE /wp-json/eventonapify/v1/events/<id>
| Setting | Methods | Route | Effect when disabled |
|---|---|---|---|
List events |
GET |
/events |
Collection reads return 403 |
Read single event |
GET |
/events/<id> |
Single-event reads return 403 |
Read RSVP summary |
GET |
/events/<id>/rsvps/summary |
RSVP summary reads return 403 |
List RSVP attendees |
GET |
/events/<id>/rsvps |
RSVP attendee reads return 403 |
Create events |
POST |
/events |
Event creation returns 403 |
Update events |
PUT, PATCH |
/events/<id> |
Event updates return 403 |
Delete events |
DELETE |
/events/<id> |
Event deletion returns 403 |
Settings -> EventON APIfy -> Event API -> RSVP cleanup on event delete (on by default). When on, permanently deleting an ajde_events post also permanently deletes the evo-rsvp records linked to it. It applies to every permanent delete path, including emptying the trash in wp-admin, WP-CLI, and other plugins, and it runs even while the Event API switch is off. DELETE /events/<id> only trashes, so it does not trigger the cleanup. Turn it off to keep RSVP records after their event is erased.
| Name | Type | Required | Description |
|---|---|---|---|
per_page |
integer | No | Items per page, default 20, max 100 |
page |
integer | No | Page number, default 1 |
search |
string | No | Search against event title/content |
status |
string | No | Comma-separated post statuses such as publish,draft |
slug |
string or array | No | Limit results to events matching one or more exact slugs; accepts a single slug, a comma-separated list, or an array (slug[]=a&slug[]=b) |
starts_on_or_after |
string | No | Only return events with an occurrence on or after this local date (YYYY-MM-DD); repeat occurrences are matched, not only the first |
starts_before |
string | No | Only return events with an occurrence strictly before this local date (YYYY-MM-DD); repeat occurrences are matched, not only the first |
upcoming |
boolean | No | When true, only return events starting today or later in the site timezone; ignored if starts_on_or_after is set |
order |
string | No | Sort direction: asc (default) or desc |
orderby |
string | No | Sort field: start_at (default), created, modified, or title |
When a date filter (starts_on_or_after, starts_before, or upcoming) is active, occurrence matching runs in PHP so repeating events are matched on any occurrence. The candidate set is capped at 2000 events (filterable via eventon_apify_occurrence_scan_limit); if the cap is reached the response includes truncated: true rather than presenting a partial result as complete.
GET /wp-json/eventonapify/v1/events/<id>/rsvps/summary is available only when the EventON - RSVP Events addon is active.
yes_submissions: number of RSVP records whose RSVP response isyesyes_attendees_total: total headcount across thoseyesrecords, using each record's storedheadcountyes_additional_attendees:yes_attendees_total - yes_submissionswaitlist_records/waitlist_attendees_total: waitlisted RSVPs, reported separately and excluded from theyestotals
Totals follow EventON's own capacity math: only published RSVP records count, records with a stored headcount of 0 are skipped, and waitlisted records are bucketed separately rather than counted as yes. Pass repeat_interval to scope the summary to a single occurrence.
Example:
curl -u your_username:your_app_password \
"https://your-site.com/wp-json/eventonapify/v1/events/123/rsvps/summary"GET /wp-json/eventonapify/v1/events/<id>/rsvps is available only when the EventON - RSVP Events addon is active.
| Name | Type | Required | Description |
|---|---|---|---|
per_page |
integer | No | Items per page, default 50, max 100 |
page |
integer | No | Page number, default 1 |
search |
string | No | Search attendee names, email, phone, RSVP fields, and custom RSVP fields |
rsvp |
string | No | all, yes, no, maybe, or waitlist; default all |
status |
string | No | Exact RSVP attendee status filter; default all |
repeat_interval |
integer | No | Limit to RSVPs for a single repeat occurrence index |
updated_after |
string | No | ISO 8601 UTC checkpoint for delta sync, for example 2026-04-08T18:00:00Z |
updated_after_id |
integer | No | RSVP ID tie-breaker used with updated_after; default 0 |
Each attendee item exposes:
idcreated_atupdated_atfirst_namelast_namefull_nameemailphoneemail_updatesrsvpstatusrsvp_typecount(displayed party size, at least1)headcount(raw stored party size;0excludes the record from summary totals)event_timerepeat_intervalother_attendeescustom_fields
When updated_after is present, delta results are ordered by updated_at ASC, id ASC and filtered using lexicographic checkpoint semantics:
- records with
updated_atlater than the supplied checkpoint are returned - when multiple records share the same
updated_at, only records withid > updated_after_idare returned updated_aftercannot be combined withrsvp,status, orsearch; those combinations return400
Delta responses add:
has_moresync_checkpoint.updated_atsync_checkpoint.id
Phase 1 deletion note: delta sync currently covers created and updated RSVP rows. If RSVP deletions need to be reconciled, run a periodic full sync.
Example delta request:
curl -u your_username:your_app_password \
"https://your-site.com/wp-json/eventonapify/v1/events/123/rsvps?updated_after=2026-04-08T18:00:00Z&updated_after_id=980&per_page=100&page=1"Example full RSVP attendee read:
curl -u your_username:your_app_password \
"https://your-site.com/wp-json/eventonapify/v1/events/123/rsvps?per_page=100&page=1"| Name | Type | Required on create | Description |
|---|---|---|---|
title |
string | Yes | Event title |
slug |
string | No | Desired URL slug; sanitized and stored as the WordPress post_name. Omit to let WordPress derive it from the title. WordPress may append a suffix to keep it unique. |
description |
string | No | Event content/body |
status |
string | No | publish, draft, private, pending, or future |
excerpt |
string | No | WordPress post excerpt |
featured_media |
integer | No | WordPress image attachment ID for the featured image; upload media first, then send the attachment ID, or send 0 to clear it |
start_date / start_time |
string | Yes | Event start in YYYY-MM-DD and HH:MM |
end_date / end_time |
string | No | Event end in YYYY-MM-DD and HH:MM |
start_at / end_at |
string | No | ISO datetime aliases accepted on input |
timezone or timezone_key |
object/string | No | Event timezone, for example America/Los_Angeles |
event_status |
string | No | scheduled, cancelled, movedonline, postponed, rescheduled, preliminary, tentative |
status_reason |
string | No | EventON status reason text |
attendance_mode |
string | No | offline, online, or mixed |
location |
object | No | EventON event_location term payload |
organizers |
array | No | EventON event_organizer terms |
event_color / event_color_secondary |
string | No | Hex colors, with or without # |
event_type |
array or string | No | Event type terms as array or comma-separated string |
tags |
array or string | No | Post tags as array or comma-separated string |
flags |
object | No | EventON yes/no flags such as featured, generate_gmap, hide_end_time |
virtual |
object | No | Virtual event metadata such as URL, password, embed, and visible end |
repeat |
object | No | Repeat settings, including frequency, count, and custom intervals |
rsvp |
object | No | RSVP addon metadata such as capacity and repeat capacities |
Preferred payloads use nested location, organizers, virtual, repeat, rsvp, and flags objects. Legacy flat aliases like location_name, location_address, map_url, and organizer are still accepted for backward compatibility.
Read responses also expose flags.all_day (read-only, derived from time_extend_type of dl), featured_media (the featured image attachment ID, so a read-modify-write clone keeps its image), and a read-only access_control object reporting ARMember membership gating: restricted (boolean), provider (armember or empty), membership_plan_ids (integer array), and restricted_by (post and/or term). It reflects configuration, not per-user authorization, returns the empty state when ARMember is inactive, and is rejected on write.
curl -u your_username:your_app_password \
-X POST "https://your-site.com/wp-json/eventonapify/v1/events" \
-H "Content-Type: application/json" \
-d '{
"title": "Ride to Big Bear",
"slug": "ride-to-big-bear",
"description": "Optional HTML content",
"excerpt": "Short summary",
"featured_media": 456,
"status": "publish",
"start_date": "2026-04-01",
"start_time": "09:00",
"end_date": "2026-04-01",
"end_time": "17:00",
"timezone": {
"key": "America/Los_Angeles",
"text": "PT"
},
"event_status": "scheduled",
"attendance_mode": "offline",
"location": {
"name": "Big Bear Lake",
"address": "123 Main St",
"city": "Big Bear Lake",
"state": "CA",
"country": "US",
"link": "https://maps.google.com/?q=Big+Bear+Lake"
},
"organizers": [
{
"name": "EventON APIfy",
"email": "events@example.com"
}
],
"event_color": "#ff0000",
"event_type": ["Rides", "Featured"],
"tags": ["bike night", "ducati"],
"flags": {
"featured": true,
"generate_gmap": true,
"open_google_maps_link": true
},
"rsvp": {
"enabled": true,
"capacity_enabled": true,
"capacity_count": 75
}
}'curl -u your_username:your_app_password \
-X POST "https://your-site.com/wp-json/wp/v2/ajde_events" \
-H "Content-Type: application/json" \
-d '{
"title": "Ride to Big Bear",
"status": "publish",
"custom_fields": {
"featured_media": 456,
"start_date": "2026-04-01",
"start_time": "09:00",
"end_date": "2026-04-01",
"end_time": "17:00",
"timezone": {
"key": "America/Los_Angeles",
"text": "PT"
}
}
}'{
"total": 14,
"pages": 2,
"page": 1,
"per_page": 10,
"events": [
{
"id": 123,
"title": "Ride to Big Bear",
"status": "publish",
"slug": "ride-to-big-bear",
"description": "Optional HTML content",
"excerpt": "Short summary",
"start_timestamp": 1775034000,
"start_at": "2026-04-01T09:00:00-07:00",
"start_date": "2026-04-01",
"start_time": "09:00",
"end_timestamp": 1775062800,
"end_at": "2026-04-01T17:00:00-07:00",
"end_date": "2026-04-01",
"end_time": "17:00",
"location": {
"term_id": 55,
"name": "Big Bear Lake",
"address": "123 Main St",
"city": "Big Bear Lake",
"state": "CA",
"country": "US",
"map_url": "https://www.google.com/maps?q=123%20Main%20St"
},
"organizer": "EventON APIfy",
"organizers": [
{
"term_id": 12,
"name": "EventON APIfy",
"email": "events@example.com"
}
],
"event_color": "#ff0000",
"event_type": ["Rides", "Featured"],
"event_type_terms": [
{ "term_id": 7, "name": "Rides", "slug": "rides" },
{ "term_id": 8, "name": "Featured", "slug": "featured" }
],
"tags": ["bike night", "ducati"],
"tag_terms": [
{ "term_id": 31, "name": "bike night", "slug": "bike-night" },
{ "term_id": 32, "name": "ducati", "slug": "ducati" }
],
"event_status": "scheduled",
"attendance_mode": "offline",
"featured_image": ""
}
]
}Each event also includes event_type_terms and tag_terms arrays carrying term_id, name, and slug for every term. The label-only event_type and tags arrays remain for backward compatibility.
- Endpoint disabled in settings:
{
"code": "eventon_apify_disabled",
"message": "The EventON APIfy endpoint is disabled. Enable it in Settings > EventON APIfy.",
"data": {
"status": 403
}
}- Invalid date/time combination:
{
"code": "eventon_apify_invalid_start_datetime",
"message": "The start_date/start_time combination could not be parsed.",
"data": {
"status": 400
}
}- EventON not active (a dependency outage; retry once EventON is back):
{
"code": "eventon_apify_eventon_missing",
"message": "EventON is not active or the ajde_events post type is unavailable.",
"data": {
"status": 503
}
}- Repeat input over a limit (see
eventon_apify_repeat_limitsunder Developer hooks):
{
"code": "eventon_apify_repeat_limit_exceeded",
"message": "repeat.count must not exceed 500.",
"data": {
"status": 400
}
}- Capability disabled in settings:
{
"code": "eventon_apify_capability_disabled",
"message": "Create events is disabled in Settings > EventON APIfy.",
"data": {
"status": 403
}
}| Hook | Type | Arguments | When |
|---|---|---|---|
eventon_apify_event_saved |
action | int $post_id, array $params, bool $created |
After an eventonapify/v1 create or update has fully succeeded (not on rollback). |
eventon_apify_event_deleted |
action | int $post_id, WP_Post $post |
After DELETE /events/<id> has moved an event to the trash. |
eventon_apify_format_event |
filter | array $event, WP_Post $post |
Each formatted event, before it is returned. Also feeds the wp/v2 compatibility fields, which are redacted after this filter. |
eventon_apify_format_rsvp_attendee |
filter | array $attendee, WP_Post $post |
Each formatted RSVP attendee, before it is returned. |
eventon_apify_validate_event_payload |
filter | true $result, array $params, bool $is_create, int $post_id |
After every built-in check passes. Return a WP_Error to reject the write; any other value lets it through. |
eventon_apify_repeat_limits |
filter | array $limits |
Upper bounds for repeat_count (500), repeat_gap (365), and repeat_intervals items (500). Requests over a limit return 400 eventon_apify_repeat_limit_exceeded. |
eventon_apify_occurrence_scan_limit |
filter | int $limit |
Maximum candidate events scanned for a date-range list filter (2000). |
This repository is set up for a dual distribution model:
- GitHub Releases is the active channel and publishes the packaged zip used by direct installs.
- Git Updater can track this repository and install updates from the attached GitHub release asset because the main plugin file includes the required Git Updater headers.
- WordPress.org is intended as a secondary channel later; it is not the active install or release path yet.
- mcp-wp-cpt
- Other WordPress plugins:
Licensed under GPL-2.0-or-later.