REST API Reference
Updated
Endpoints and hooks marked (Pro) require WPMediaVerse Pro. Everything documented below ships in the free plugin.
Base URL: /wp-json/mvs/v1/
All routes below use the mvs/v1 namespace (the messaging routes share the same namespace).
Authentication. Reads of public data are open. Every write — and every /me/* route — requires an authenticated user. Pass the X-WP-Nonce header with a nonce generated via wp_create_nonce( 'wp_rest' ) and send cookies with credentials: 'same-origin', or use a WordPress Application Password for non-browser clients.
Private-community gate (2.2.0). When the host community is private, the entire mvs/v1 surface — including public reads — requires authentication, and with Pro active the mvs-pro/v1 namespace is covered by the same gate. Unauthenticated requests get 401 with code mvs_community_private. The gate is off by default and controlled by three filters: mvs_rest_require_auth (return true to arm the gate; BuddyNext arms it automatically when its private-community mode is on), mvs_rest_can_access (defaults to is_user_logged_in(); override to allow specific unauthenticated callers, e.g. a trusted server-to-server integration), and mvs_rest_gated_route_prefixes (the covered route prefixes; Pro appends its own namespace here, and sites can add more).
Authorization model. Three levels are used throughout:
- Public — no auth; privacy is enforced inside the query so private rows never leak.
- Authenticated — any logged-in user (
is_user_logged_in()). - Capability — a specific capability such as
upload_mvs_media,moderate_mvs_media, ormanage_mvs_access.
Update methods. Every route documented below with PUT also accepts PATCH and POST. WordPress registers these three together as its "editable" method group, so all three reach the same handler with the same arguments and the same response. PUT is used throughout this page as the canonical form; pick whichever your HTTP client handles most comfortably.
Rate limiting. Many routes are throttled per user/IP (the limit is noted where it is unusually tight). Exceeding a limit returns 429 Too Many Requests.
Media
GET /media
Auth: Public (privacy enforced in query). Rate-limited to 120/min.
List media items. Returns only rows the caller is allowed to see.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 |
Page number |
per_page |
int | 20 |
Items per page (max: 100, filterable via mvs_rest_pagination_max) |
media_type |
string | (all) | Filter by type: image, video, audio, document |
author |
int | (all) | Filter by user ID |
slug |
string | (none) | Fetch a single item by post slug |
tag |
string | (all) | Filter by mvs_tag slug |
category |
string | (all) | Filter by mvs_category slug |
orderby |
string | date |
Sort: date, trending, popular (filterable via mvs_feed_sort_options) |
scope |
string | public |
public or all (owner/privileged callers) |
s |
string | (none) | Full-text search term |
group_covers |
bool | false |
Collapse gallery groups to a single cover item |
Response:
{
"items": [
{
"id": 123,
"title": "Sunset Photo",
"description": "",
"media_type": "image",
"file_url": "https://example.com/wp-content/uploads/wpmediaverse/2025/03/photo.jpg",
"privacy": "public",
"author_id": 1,
"album_id": null,
"views": 42,
"reactions_count": 5,
"comments_count": 2,
"created_at": "2025-03-27T12:00:00Z",
"can_edit": false,
"is_favorited": false,
"viewer_reaction": null
}
],
"total": 50,
"pages": 5
}
Viewer-aware fields. Every media item carries three fields resolved against the requesting user, not cached statically: can_edit (bool — true when the viewer is the author or has manage_options), is_favorited (bool), and viewer_reaction (string reaction slug, or null if the viewer hasn't reacted). All three are false/null for anonymous requests. List endpoints batch-load this state per page (MediaController::prime_viewer_state()) rather than querying per row.
POST /media
Auth: Capability — upload_mvs_media (or manage_options).
Upload a new media file.
Body (multipart/form-data):
| Field | Required | Description |
|---|---|---|
file |
Yes | The file to upload |
title |
No | Media title (defaults to filename) |
description |
No | Text description |
privacy |
No | Privacy level (default: site setting) |
group_id |
No | BuddyPress group ID (required when privacy=group) |
album_id |
No | Add to this album after upload |
is_story |
No | true to mark as a story |
Response: 201 Created with the new media object.
GET /media/{id}
Auth: Public (privacy check in permission callback). Returns 403 if the caller cannot view it, 404 if it does not exist.
Get a single media item.
PUT /media/{id}
Auth: Capability — owner with edit_mvs_medias, or edit_others_mvs_medias.
Update a media item.
Body (JSON):
{
"title": "Updated Title",
"description": "Updated description",
"privacy": "private"
}
DELETE /media/{id}
Auth: Capability — owner with delete_mvs_medias, or delete_others_mvs_medias.
Delete a media item and its stored file.
POST /media/{id}/replace
Auth: Capability — same as PUT /media/{id} (edit permission).
Replace the underlying file of an existing media item while keeping its ID, comments, reactions, and stats. Send the new file as multipart/form-data with a file field.
POST /media/{id}/view
Auth: Public.
Record a view for the item. Increments the view counter and writes a row to mvs_media_views.
POST /media/{id}/download
Auth: Public. Rate-limited to 30/min.
Record a download event and increment mvs_media_stats.downloads. Refused with 403 when the global Allow Downloads toggle is off OR the per-media allow_download meta is '0'.
POST /media/{id}/share
Auth: Public. Rate-limited to 60/min.
Record a share event and increment mvs_media_stats.shares. Called by the lightbox Share button after a successful navigator.share / clipboard copy.
GET /media/{id}/access
Auth: Public.
Report whether the current user can view the item (resolves privacy rules and any access grants). Returns an access decision, not the file.
Response:
{
"media_id": 123,
"can_view": true,
"privacy": "members",
"is_owner": false
}
privacy and is_owner are only populated when the caller can view the item or owns it; a caller with neither returns 403 before these fields are built.
GET /media/{id}/group
Auth: Public.
Return every item that belongs to the same gallery/upload group as {id} (used to build multi-item lightboxes).
GET /media/{id}/signed-url
Auth: Authenticated with view access to the media.
Generate a time-limited signed URL for a private file. The signed URL points at /serve and carries an HMAC-SHA256 signature binding the request to a user, media ID, and expiry.
| Parameter | Type | Default | Description |
|---|---|---|---|
download |
bool | false |
Issue a download (attachment) URL |
ttl |
int | (setting) | Override the signed-URL lifetime, in seconds |
GET /serve
Auth: Public — the HMAC signature on the URL is the credential (analogue of an S3 pre-signed URL). For non-public media the handler also re-checks can_view per request.
Stream the underlying file (full file or a thumbnail variant) for a validated signed URL. Drains output buffers and disables zlib.output_compression before streaming so byte counts match Content-Length. Honours Range: headers for video/audio.
| Param | Required | Description |
|---|---|---|
mvs_id |
yes | Media ID |
mvs_uid |
yes | User ID the URL was signed for (0 for anonymous public media) |
mvs_exp |
yes | Unix expiration timestamp |
mvs_sig |
yes | HMAC-SHA256 signature |
mvs_size |
no | large / medium / thumbnail / watermark to serve a variant |
mvs_dl |
no | When 1, sets Content-Disposition: attachment and increments the download counter |
GET /me/media
Auth: Authenticated.
List the current user's own media, including private and pending items. Accepts the same parameters as GET /media.
Albums
GET /albums
Auth: Public (privacy enforced in query).
List albums. Supports page, per_page, author, orderby, order.
POST /albums
Auth: Authenticated with album-create permission.
Create an album.
{
"title": "My Album",
"description": "Optional",
"privacy": "public"
}
GET /albums/{id}
Auth: Public (private albums 404 for non-owners).
Get an album with its media list.
PUT /albums/{id}
Auth: Authenticated — owner / edit permission.
Update an album.
DELETE /albums/{id}
Auth: Authenticated — owner / delete permission.
Delete an album (does not delete the media items it contains).
PUT /albums/{id}/reorder
Auth: Authenticated — owner / edit permission.
Reorder the items inside an album.
{ "order": [103, 101, 102] }
POST /albums/{id}/items
Auth: Authenticated — owner / edit permission.
Add media to an album.
{ "media_ids": [101, 102, 103] }
DELETE /albums/{id}/items/{media_id}
Auth: Authenticated — owner / edit permission.
Remove a single media item from an album.
PUT /albums/{id}/cover
Auth: Authenticated — owner / edit permission.
Set the album cover.
{ "media_id": 101 }
Collections
GET /collections
Auth: Authenticated. Returns the current user's collections.
POST /collections
Auth: Authenticated.
Create a collection (manual or smart).
{
"title": "Nature",
"type": "smart",
"rules": { "tags": ["nature"], "media_type": "image" },
"privacy": "public"
}
GET /collections/{id}
Auth: Public (privacy check in permission callback).
Get a collection with its resolved item list.
PUT /collections/{id}
Auth: Authenticated — owner only.
Update a collection.
DELETE /collections/{id}
Auth: Authenticated — owner only.
Delete a collection.
PUT /collections/{id}/rules
Auth: Authenticated — owner only.
Set the smart-collection rules used to resolve its items.
{ "rules": [ { "field": "tag", "value": "nature" } ] }
Reactions
All reaction operations live on a single route that varies by HTTP method.
GET /media/{id}/reactions
Auth: Public.
Get reaction counts grouped by type. When a logged-in user calls it, the response also indicates that user's own reaction.
POST /media/{id}/reactions
Auth: Authenticated.
Add or change your reaction. Note the field name is reaction_type.
{ "reaction_type": "love" }
DELETE /media/{id}/reactions
Auth: Authenticated.
Remove your reaction.
Comments
GET /media/{id}/comments
Auth: Public (visibility follows the parent media's privacy).
List comments. Supports page, per_page (max 100).
POST /media/{id}/comments
Auth: Authenticated. Use @username syntax for mentions.
{
"content": "Great photo @jane!",
"parent": 0,
"from_activity": 0
}
| Field | Required | Description |
|---|---|---|
content |
Yes | Comment text |
parent |
No | Parent comment ID for threaded replies (default 0) |
from_activity |
No | Source BuddyPress activity ID, when posted from the activity stream |
PUT /media/{id}/comments/{comment_id}
Auth: Authenticated — owner (within the edit window) or moderate_mvs_media.
Edit a comment.
DELETE /media/{id}/comments/{comment_id}
Auth: Authenticated — owner or moderate_mvs_media.
Delete a comment.
Favorites
GET /media/{id}/favorite
Auth: Authenticated.
Return whether the current user has favorited the item.
POST /media/{id}/favorite
Auth: Authenticated.
Add the item to favorites (toggles on). Optional collection_id saves it into a specific collection.
DELETE /media/{id}/favorite
Auth: Authenticated.
Remove the item from favorites.
GET /me/favorites
Auth: Authenticated.
List the current user's favorites. Supports collection_id, page, per_page.
Access Control & Grants
These routes manage per-media access rules and direct user grants. All require the media owner or the manage_mvs_access capability.
GET /media/{media_id}/rules
Auth: Owner or manage_mvs_access.
List the access rules attached to a media item.
POST /media/{media_id}/rules
Auth: Owner or manage_mvs_access. Rate-limited to 30/min.
Replace the full rule set for a media item.
{
"rules": [
{ "rule_type": "follower", "rule_value": "1" },
{ "rule_type": "purchase", "rule_value": "1", "price": 4.99, "currency": "USD" }
]
}
Each rule's rule_type must be one of AccessRulesService::RULE_TYPES.
DELETE /media/{media_id}/rules/{rule_id}
Auth: Owner or manage_mvs_access.
Delete a single access rule.
POST /media/{media_id}/grant
Auth: Owner or manage_mvs_access.
Grant a specific user access to the media.
{
"user_id": 55,
"source": "manual",
"expires_at": "2026-01-01T00:00:00Z"
}
source defaults to manual and must be one of AccessRulesService::GRANT_SOURCES.
DELETE /media/{media_id}/grant/{user_id}
Auth: Owner or manage_mvs_access.
Revoke a user's grant.
GET /me/grants
Auth: Authenticated.
List the media the current user has been granted access to.
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
int | 20 |
Items per page (max: 100) |
page |
int | 1 |
Page number |
active_only |
bool | true |
Exclude expired grants |
Follows
POST /users/{id}/follow
Auth: Authenticated. Rate-limited to 30/min.
Follow a user.
DELETE /users/{id}/follow
Auth: Authenticated.
Unfollow a user.
GET /users/{id}/followers
Auth: Public.
List a user's followers (display name + avatar).
GET /users/{id}/following
Auth: Public.
List who a user follows.
GET /me/following
Auth: Authenticated.
List who the current user follows.
GET /me/followers
Auth: Authenticated.
List the current user's followers.
User Profile
GET /me/profile
Auth: Authenticated.
Get the current user's profile.
PUT /me/profile
Auth: Authenticated.
Update profile fields.
{
"first_name": "Jane",
"last_name": "Smith",
"display_name": "jsmith",
"description": "Photographer"
}
POST /me/avatar
Auth: Authenticated.
Upload a new profile avatar (multipart/form-data, file field).
DELETE /me/avatar
Auth: Authenticated.
Remove the custom avatar and revert to Gravatar.
Users
GET /users/{id}
Auth: Public. Rate-limited to 60/min.
Get a user's public profile: bio, avatar URL, follower/following counts, and public media count. user_login / user_registered are returned only to the user themselves or to admins (enumeration hardening).
GET /users/{id}/media
Auth: Public (privacy enforced in query).
List a user's visible media. Supports page, per_page.
GET /users/search
Auth: Public.
Search for users by display name or username.
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | (required) | Search term |
per_page |
int | 10 |
Results per page (max: 50) |
Reports & Blocking
POST /media/{id}/report
Auth: Authenticated. Rate-limited to 10/min.
Submit a content report against a media item.
{
"reason": "inappropriate",
"details": "Optional explanation"
}
reason must be one of ReportService::REASONS.
POST /users/{id}/report
Auth: Authenticated.
Report a user. Same reason / details body as media reports.
POST /users/{id}/block
Auth: Authenticated.
Block a user.
DELETE /users/{id}/block
Auth: Authenticated.
Unblock a user.
GET /me/blocked
Auth: Authenticated.
List the users the current user has blocked.
Moderation
All moderation routes require the moderate_mvs_media capability.
GET /moderation
List flagged / pending media items. Supports collection params (page, per_page).
The route is
/moderation, not/moderation/queue. Earlier revisions of this page showed/moderation/queue, which returns404.
GET /moderation/counts
Return queue counts (pending, flagged, etc.) for building badges and tabs.
POST /moderation/{id}/approve
Approve a media item.
POST /moderation/{id}/reject
Reject a media item. Optional reason string is recorded.
POST /moderation/{id}/analyze
Trigger AI analysis (description / tagging / safety) on a media item.
GET /ai/usage
Return AI usage / budget figures for the moderation dashboard.
Bulk Operations
POST /media/bulk
Auth: Authenticated with the relevant per-action capability. Rate-limited to 10/min, max 100 IDs per call.
Perform a bulk action on multiple media items.
{
"action": "delete",
"media_ids": [101, 102, 103]
}
| Field | Required | Description |
|---|---|---|
action |
Yes | One of delete, move_to_album, change_privacy |
media_ids |
Yes | Array of media IDs (max 100) |
album_id |
When action=move_to_album |
Destination album ID |
privacy |
When action=change_privacy |
New privacy value |
Stats
GET /media/{id}/stats
Auth: Public for public media; 403 for media the caller cannot view.
Per-item statistics (views, reactions, comments, downloads).
GET /me/stats
Auth: Authenticated.
Aggregate statistics across the current user's own media.
Tags
GET /tags
Auth: Public.
List / autocomplete mvs_tag terms.
| Parameter | Type | Default | Description |
|---|---|---|---|
search |
string | (none) | Filter by name |
per_page |
int | 20 |
Results per page (max: 100) |
orderby |
string | name |
name or count |
POST /tags
Auth: Capability — create_tag_permissions_check (any user who can upload media).
Create a new tag. Body: name (required), optional slug.
GET /tags/cloud
Auth: Public.
Return top tags with usage counts for a tag cloud. Optional limit (default 50, max 200).
POST /tags/merge
Auth: Capability — admin (moderate_mvs_media).
Merge one tag into another. All media on source_id are re-tagged with target_id and source_id is deleted.
{ "source_id": 12, "target_id": 7 }
PUT /tags/{id}
Auth: Capability — admin.
Rename a tag.
{ "name": "New Tag Name" }
DELETE /tags/{id}
Auth: Capability — admin.
Delete a tag.
Notifications
GET /me/notifications
Auth: Authenticated.
List the current user's notifications.
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
int | 20 |
Items per page (max: 100) |
page |
int | 1 |
Page number |
filter |
string | all |
Filter set (e.g. all, unread) |
The total count is returned in the X-WP-Total header.
GET /me/notifications/count
Auth: Authenticated.
Return the current user's unread notification count.
POST /me/notifications/read
Auth: Authenticated.
Mark notifications as read. Pass an ids array to mark specific notifications, or omit it to mark all as read.
{ "ids": [12, 13, 14] }
Devices / push tokens
Register a member's device so the site can deliver push notifications for new in-app notifications. Added in 2.4.0. Both routes require an authenticated member and upsert into the mvs_device_tokens table. This is the Free generic device-token surface, backed by Social/PushService.php; when a new in-app notification is created, PushService fires the action mvs_push_send( int $user_id, array $tokens, array $payload ) for a push-delivery integration to send, gated by the filter mvs_push_should_send (return false to suppress). Pro's Expo push at POST /mvs-pro/v1/push/register-device (table mvs_pro_push_devices) is separate and additional — neither replaces the other.
POST /me/devices
Auth: Authenticated.
Register (upsert) a device push token for the current member.
| Field | Required | Description |
|---|---|---|
platform |
Yes | One of ios, android, web |
token |
Yes | The push token string |
{ "platform": "ios", "token": "abc123..." }
DELETE /me/devices
Auth: Authenticated.
Unregister a device push token.
| Field | Required | Description |
|---|---|---|
token |
Yes | The push token string to remove |
{ "token": "abc123..." }
Native App & Interests
Added in 1.9.0 to support a native mobile/headless client: a public pre-login config call, an interest-picker onboarding flow, and "people you may know" suggestions. See mvs_app_config_features and related filters for how Free/Pro contribute to /app/config.
GET /app/config
Auth: Public.
Single call a client makes before theming itself and deciding which feature surfaces to mount. Returns only what the core /wp-json/ index cannot express (branding + feature flags) — site name, description, icon, and auth come from the core index, never restated here.
Response:
{
"accent_color": null,
"logo_url": null,
"login_bg_url": null,
"dark_mode_default": false,
"layout": "grid",
"pro_active": true,
"features": {
"messaging": true,
"reactions": true,
"comments": true,
"favorites": true,
"albums": true,
"collections": true,
"follows": true,
"notifications": true,
"activity": true
}
}
features.messaging is false when mvs_dm_access is nobody/disabled/none. Pro extends features with its own toggles (battles, challenges, tournaments, boosts, streaks, video, stories, …) and can populate accent_color / logo_url / login_bg_url / dark_mode_default / layout from its Mobile App Branding settings.
GET /app/interests
Auth: Public.
Available interest chips for the onboarding picker — the top 40 mvs_category terms by usage count, each with a representative public cover thumbnail. Cached (transient, default 1 hour, filterable via mvs_app_interests_cache_ttl).
Response:
[
{ "id": 12, "name": "Nature", "slug": "nature", "count": 84, "cover_url": "https://example.com/.../serve?..." }
]
GET /me/interests
Auth: Authenticated.
The current user's saved interest picks.
Response:
{ "interest_ids": [12, 19] }
POST /me/interests
Auth: Authenticated.
Save the current user's interest picks. Only valid mvs_category term IDs are kept; unknown IDs are silently dropped. Saving also marks the viewer as onboarded (mvs_onboarded user meta), so a client doesn't have to separately call /me/onboarding/complete.
{ "interest_ids": [12, 19, 4] }
| Field | Required | Description |
|---|---|---|
interest_ids |
Yes | Array of mvs_category term IDs |
Response: { "interest_ids": [12, 19, 4] } (filtered to valid term IDs).
GET /users/suggested
Auth: Authenticated.
"People you may know" — ranked creators (popularity + interest overlap with the viewer's picks), excluding the viewer, users they already follow, and blocked users. Each result carries up to 3 sample public-media thumbnails for the suggestion card.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | (service default) | Max results, clamped to 1-50 |
Response:
[
{
"id": 55,
"name": "Jane Smith",
"avatar": "https://example.com/avatar.jpg",
"profile_url": "https://example.com/media/@jane/",
"follower_count": 120,
"is_following": false,
"sample_media": ["https://example.com/.../thumb1.jpg"]
}
]
The candidate pool is cached (default 1 hour, filterable via mvs_suggestions_cache_ttl).
POST /me/onboarding/complete
Auth: Authenticated.
Explicitly flag the current user as onboarded (mvs_onboarded user meta), for clients whose first-session flow doesn't end with saving interests. Idempotent.
Response: { "onboarded": true }
Admin
POST /admin/welcome/dismiss
Auth: Authenticated (per-user state).
Dismiss the admin welcome banner for the current user.
Dismissible notices
POST /me/dismiss
Auth: Authenticated.
Record that the current member has closed a dismissible frontend banner, so it never renders again for them on any device. Server-side rather than localStorage, because a banner painted and then removed by JavaScript collapses the layout under the reader's cursor.
| Field | Required | Description |
|---|---|---|
key |
Yes | Identifier of the banner being dismissed (sanitized with sanitize_key) |
{ "key": "profile_prompt" }
Activity Feed
GET /feed
Auth: Public (private events never appear; following scope is empty for anonymous callers).
Return the activity feed.
| Parameter | Type | Default | Description |
|---|---|---|---|
scope |
string | public |
public (all public media) or following (followed users) |
per_page |
int | 20 |
Items per page (max: 100) |
page |
int | 1 |
Page number |
GET /users/{id}/activity
Auth: Public.
Return a user's public activity (uploads, album creations, reactions). Supports page, per_page.
Messaging
Direct-messaging routes share the mvs/v1 namespace. All require authentication. Conversations started by users you do not follow land in the Requests tab until accepted or declined.
GET /me/conversations
List the current user's conversations.
| Parameter | Type | Default | Description |
|---|---|---|---|
tab |
string | all |
all, unread, or requests |
per_page |
int | 20 |
Conversations per page (max: 50) |
page |
int | 1 |
Page number |
POST /conversations
Start a new conversation.
{ "recipient_id": 42, "as_request": false }
| Field | Required | Default | Description |
|---|---|---|---|
recipient_id |
Yes | - | User ID to start (or resume) a conversation with |
as_request |
No | false |
When true, force the conversation to open as a pending message request (lands in the recipient's Requests tab and must be accepted/declined) instead of an active thread — even if the sender/recipient relationship would otherwise allow a direct thread. Lets a native app open a "message request" flow explicitly through mvs/v1 alone (1.8.0). |
Response: 201 Created with the new conversation object.
GET /conversations/{id}
Get a single conversation's metadata and participants.
PATCH /conversations/{id}
Update the current user's per-conversation preferences.
{
"is_muted": true,
"is_pinned": false,
"is_archived": false
}
DELETE /conversations/{id}
Leave (soft-delete) the conversation for the current user.
GET /conversations/{id}/messages
List messages in a conversation (newest-first).
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
int | 30 |
Messages per page (max: 100) |
before |
int | 0 |
Return messages with ID less than this (cursor pagination) |
POST /conversations/{id}/messages
Send a message.
{
"content": "Hey, love the photo!",
"message_type": "text",
"media_id": null,
"attachment_id": null,
"parent_id": null,
"metadata": {}
}
| Field | Required | Description |
|---|---|---|
content |
Yes (unless an attachment/media is sent) | Message text |
message_type |
No | text (default) or media |
media_id |
No | Attach an existing media post |
attachment_id |
No | Attach a file uploaded via POST /messages/upload |
parent_id |
No | Reply to this message ID |
metadata |
No | Arbitrary structured metadata |
POST /conversations/{id}/read
Mark all messages in the conversation as read for the current user.
POST /conversations/{id}/typing
Send a typing-indicator event (no persistent storage; fires a real-time event only).
POST /conversations/{id}/accept
Accept a message request — moves the conversation from Requests to All.
POST /conversations/{id}/decline
Decline a message request — removes the conversation from the inbox.
DELETE /messages/{id}
Soft-delete a message for the current user (content hidden, record retained).
DELETE /messages/{id}/unsend
Hard-delete (unsend) a message. Only available within the edit window and only for the message owner.
POST /messages/{id}/reactions
Add an emoji reaction to a message.
{ "emoji": "heart" }
DELETE /messages/{id}/reactions
Remove your emoji reaction from a message.
POST /messages/upload
Upload an attachment for use in a DM. Returns a reference ID to pass as attachment_id (or media_id) when sending the message. Body: multipart/form-data with a single file field.
{ "media_id": 204, "url": "https://example.com/..." }
GET /me/messages/unread-count
Return the total unread message count for the current user.
{ "count": 3 }
GET /messages/poll
Long-poll for new messages. The server holds the connection open and responds as soon as a new message arrives or the timeout is reached.
| Parameter | Type | Default | Description |
|---|---|---|---|
since |
int | (required) | Return messages with ID greater than this value |
conversation_id |
int | 0 |
Scope the poll to a single conversation |
Error Responses
All errors follow the WP REST API error format:
{
"code": "mvs_invalid_type",
"message": "This file type is not allowed.",
"data": { "status": 400 }
}
Common error codes:
| Code | Status | Meaning |
|---|---|---|
mvs_invalid_type |
400 | MIME type not in allowed list |
mvs_file_too_large |
400 | File exceeds max upload size |
mvs_blocked_extension |
400 | Dangerous file extension |
mvs_no_ids |
400 | Bulk request had no media IDs |
mvs_duplicate |
409 | Duplicate file (when duplicate_action=skip) |
mvs_not_found |
404 | Resource not found |
mvs_user_not_found |
404 | User not found |
mvs_not_logged_in / mvs_unauthorized |
401 | Authentication required |
mvs_forbidden / rest_forbidden |
403 | Access denied by privacy/capability rules |
mvs_storage_failed |
500 | Storage driver error |
| (rate limit) | 429 | Too many requests |
Authentication
Routes for obtaining the credentials the rest of the API expects. The app-auth filters that govern this flow (mvs_app_password_login_enabled, mvs_app_connect_schemes, mvs_app_scheme, mvs_app_credential_issued) are documented in Hooks & Filters.
GET /auth/nonce
Return a fresh wp_rest nonce for the current session.
Auth: Authenticated (cookie).
{ "nonce": "a1b2c3d4e5" }
Browser clients that keep a page open longer than the nonce lifetime call this to refresh rather than reloading the page.
POST /auth/app-password
Exchange a WordPress username and password for an Application Password - the credential a mobile or desktop client actually stores.
Auth: Public by necessity. This is how a member obtains their first credential, so there is nothing to authenticate with yet.
| Parameter | Type | Required | Description |
|---|---|---|---|
username |
string | yes | Email address or username. |
password |
string | yes | Account password. |
app_name |
string | no | Name shown beside this credential in the member's profile. |
app_id |
string | no | Stable per-install id, so a repeat sign-in replaces the existing row instead of adding another. |
Because it is unauthenticated, this route is guarded harder than any other: a site-owner switch (mvs_app_password_login_enabled), a TLS requirement, uniform failure responses that do not reveal whether a username exists, the suspension gate, and rate limiting applied before any credential is read. Accounts with two-factor authentication receive 409 rather than a silent 2FA bypass.
Always send this over HTTPS. Store the returned credential in the platform keychain, never in plain preferences.
Account
DELETE /me
Schedule deletion of the authenticated member's own account.
Auth: Authenticated.
| Parameter | Type | Required | Description |
|---|---|---|---|
confirm |
string | yes | Must be the literal string DELETE. |
password |
string | conditional | The account password, re-entered. Required unless mvs_account_deletion_password_required is filtered to false. |
Deletion is not immediate. The request starts a grace period (default 30 days, filterable via mvs_account_deletion_grace_days; return 0 to delete on request). During the grace period the member can still cancel.
GET /me/deletion
Return the authenticated member's pending deletion request, if any - including when it is scheduled to execute.
Auth: Authenticated.
DELETE /me/deletion
Cancel a pending deletion request and restore the account to normal standing.
Auth: Authenticated.
These three routes are the member-facing half of GDPR erasure. The admin-facing export and erasure tools are covered in GDPR & Privacy.
GET /me/transactions
The authenticated member's own usage ledger - upload credits consumed and granted.
Auth: Authenticated.
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 |
Rows per page. |
page |
integer | 1 |
Page number. |
Pairs with the [mvs_usage_history] shortcode, which renders the same data.
Access control
GET /access/options
Return the building blocks for the access-rule builder: the site's roles, and the rule types available to members.
Auth: Authenticated.
Drives the frontend edit-modal access panel, the admin sub-page, and the mobile app - all three read this one endpoint rather than hardcoding a rule-type list. Pro extends the returned rule types through the mvs_access_rule_types_ui filter, so a client that renders whatever this endpoint returns picks up Pro's monetization and code-grant rule types with no client change.
Conversation media and search
These sit alongside the messaging routes documented above.
GET /conversations/{id}/media
Every media attachment shared in one conversation, newest first. This is what backs the "shared media" panel in a chat.
Auth: Authenticated, and the caller must be a participant.
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
per_page |
integer | 60 |
1-200 | Attachments per page. |
GET /conversations/{id}/messages/search
Full-text search within a single conversation.
Auth: Authenticated, and the caller must be a participant.
| Parameter | Type | Required | Default | Range | Description |
|---|---|---|---|---|---|
q |
string | yes | - | - | Search term. |
per_page |
integer | no | 50 |
1-100 | Results per page. |
Search is scoped to the one conversation in the path. There is no cross-conversation search endpoint.