Skip to content

REST API

WB Listora exposes 58 REST endpoints under the listora/v1 namespace. Every customer-facing surface (frontend listing UI, submission wizard, user dashboard, search, reviews, claims, favorites) is REST-driven; AJAX is reserved for admin-only operations (per the plugin’s REST-first architecture rule).

Base URL: <your-site>/wp-json/listora/v1/

Auth model:

  • Public - GET reads (listings, search, single listing). No authentication required.
  • Auth - requires a valid user session (cookies + nonce) OR a WordPress Application Password.
  • Owner - only the listing’s author (or a user with the listing’s edit capability) can modify.
  • Admin - requires manage_options or manage_listora_settings.

Nonce header for browser clients: X-WP-Nonce: <wp_create_nonce("wp_rest")>. Apps using Application Passwords don’t need this.

Response envelope (lists):

{
"listings": [ /* array of resource objects */ ],
"total": 247,
"pages": 21,
"has_more": true,
"cursor": "WyJsaXN0aW5nIiwxMjM0XQ==",
"next_cursor": "WyJsaXN0aW5nIiwxNDQ0XQ=="
}

Error contract:

{
"code": "listora_invalid_field",
"message": "Field 'price' is required",
"data": { "status": 400 }
}

Permission-denial codes: since 1.1.0, denied requests return structured codes instead of the generic rest_forbidden - listora_unauthorized with HTTP 401 when the caller is not authenticated, and listora_forbidden with HTTP 403 when an authenticated caller lacks the required capability or ownership. This applies to the Settings + notification-log endpoints (Free) and the Analytics + Photo Reviews endpoints (Pro); ownership failures correctly return 403.

Generated from audit/manifest.json. Re-run /wp-plugin-onboard --refresh after non-trivial commits to regenerate.

Method Route Auth Handler Purpose
GET /listora/v1/dashboard/listings logged_in_permissions Dashboard_Controller::get_listings User’s listings (cursor pagination). Optional listing_type=<slug> narrows the list to one listing type; a slug that is not a type on the site returns an empty list, never the unfiltered one. /dashboard/stats is deliberately NOT scoped - it reports the member across every type.
GET /listora/v1/listings Public Listings_Controller::get_items List published listings (cursor pagination)
POST /listora/v1/listings/bulk Public Listings_Controller::get_bulk Fetch up to 50 listings by ID (offline cache)
DELETE /listora/v1/listings/{id} delete_listing_permissions Listings_Controller::delete_listing Owner soft-delete
POST /listora/v1/listings/{id}/deactivate deactivate_listing_permissions Listings_Controller::deactivate_listing Owner hides their listing from the directory (sets listor…
GET /listora/v1/listings/{id}/detail Public Listings_Controller::get_listing Single listing detail (card or full). Carries owner: { name, url } - the public “Listed by” name. The key is ABSENT, not null, when the Show Who Listed It feature is off. List and card payloads omit it deliberately, to avoid a user lookup per row.
POST /listora/v1/listings/{id}/feature feature_listing_permissions Listings_Controller::feature_listing Upgrade listing to Featured
GET /listora/v1/listings/{id}/related Public Listings_Controller::get_related Related listings
POST /listora/v1/listings/{id}/renew renew_listing_permissions Listings_Controller::renew_listing Renew expired listing
GET /listora/v1/listings/{id}/renewal-quote renew_listing_permissions Listings_Controller::get_renewal_quote Renewal pricing/status
GET, POST /listora/v1/listings/{listing_id}/services __return_true / create_service_permissions Services_Controller::get_listing_services / create_service Listing services list/create
POST /listora/v1/listings/{listing_id}/services/reorder create_service_permissions Services_Controller::reorder_services Reorder services
Method Route Auth Handler Purpose
POST /listora/v1/listings/{id}/reactivate reactivate_listing_permissions Listings_Controller::reactivate_listing Owner restores a deactivated listing back to its prior pu…
Method Route Auth Handler Purpose
POST /listora/v1/listings/bulk-moderate Owner WBListora\REST\Listings_Controller::bulk_moderate Bulk moderation - approve/reject/feature/unfeature/trash …
Method Route Auth Handler Purpose
POST /listora/v1/listings/(?P<id>[\d]+)/contact-form __return_true (anonymous allowed; nonce + honeypot + Anti_Spam pipeline gate inside handler) WBListora\Contact_Form::handle_rest_submission Free’s listing contact form. Per-IP-per-listing 3/hour ca…
Method Route Auth Handler Purpose
GET /listora/v1/dashboard/reviews logged_in_permissions Dashboard_Controller::get_reviews User’s reviews received/written
PUT, DELETE /listora/v1/reviews/{id} update_review_permissions / delete_review_permissions Reviews_Controller::update_review / delete_review Update/delete review
POST /listora/v1/reviews/{id}/helpful logged_in_permissions Reviews_Controller::vote_helpful Helpful vote
POST /listora/v1/reviews/{id}/reply owner_reply_permissions Reviews_Controller::owner_reply Listing owner reply
POST /listora/v1/reviews/{id}/report logged_in_permissions Reviews_Controller::report_review Report inappropriate review
Method Route Auth Handler Purpose
GET, POST /listora/v1/listings/{listing_id}/reviews Auth Reviews_Controller::get_listing_reviews / create_review List reviews / submit new review
Method Route Auth Handler Purpose
GET /listora/v1/search Public Search_Controller::search Filtered/geo/fulltext/facet search
GET /listora/v1/search/suggest Public Search_Controller::suggest Autocomplete suggestions
Method Route Auth Handler Purpose
POST /listora/v1/submission/resend-verification Public Submission_Controller::resend_verification_endpoint Resend email verification. Deprecated in 1.3.0 with guest submission; submission now requires a logged-in account, so this endpoint is no longer used.
GET /listora/v1/submission/verify Public Submission_Controller::verify_endpoint REST mirror of email verify URL. Deprecated in 1.3.0 with guest submission; submission now requires a logged-in account, so this endpoint is no longer used.
Method Route Auth Handler Purpose
GET, POST /listora/v1/claims admin_permissions / logged_in_permissions Claims_Controller::get_claims / submit_claim List all claims (admin) / submit claim
PUT /listora/v1/claims/{id} admin_permissions Claims_Controller::update_claim Approve/reject claim
GET /listora/v1/dashboard/claims logged_in_permissions Dashboard_Controller::get_my_claims User’s claim requests
Method Route Auth Handler Purpose
GET, POST /listora/v1/favorites logged_in_permissions Favorites_Controller::get_favorites / add_favorite List/add favorites
DELETE /listora/v1/favorites/{listing_id} logged_in_permissions Favorites_Controller::remove_favorite Remove favorite
Method Route Auth Handler Purpose
GET, PUT, DELETE /listora/v1/services/{id} scoped Services_Controller::get_service / update_service / delete_service CRUD single service
Method Route Auth Handler Purpose
GET /listora/v1/dashboard/notifications logged_in_permissions Dashboard_Controller::get_notifications In-app notifications
PUT /listora/v1/dashboard/notifications/read logged_in_permissions Dashboard_Controller::mark_notifications_read Mark notifications read
GET, PUT /listora/v1/dashboard/profile logged_in_permissions Dashboard_Controller::get_profile / update_profile Dashboard profile (name, bio)
GET /listora/v1/dashboard/stats logged_in_permissions Dashboard_Controller::get_stats User dashboard stats (60s transient)
Method Route Auth Handler Purpose
GET, POST /listora/v1/listing-types __return_true / create_item_permissions_check Listing_Types_Controller::get_items / create_item List/create listing types
GET, PUT, DELETE /listora/v1/listing-types/{slug} scoped Listing_Types_Controller::get_item / update_item / delete_item CRUD single listing type
GET /listora/v1/listing-types/{slug}/categories Public Listing_Types_Controller::get_categories Categories scoped to a listing type
GET /listora/v1/listing-types/{slug}/fields Public Listing_Types_Controller::get_fields Type fields schema
Method Route Auth Handler Purpose
GET, PUT, DELETE /listora/v1/settings Admin Settings_Controller::get_all_settings / update_settings / reset_settings Plugin settings CRUD
GET /listora/v1/settings/app-config Public Settings_Controller::get_app_config Public bootstrap config (app/frontend)
GET /listora/v1/settings/export Admin Settings_Controller::export_settings Export settings JSON
POST /listora/v1/settings/import Admin Settings_Controller::import_settings Import settings JSON
GET /listora/v1/settings/maps Public Settings_Controller::get_map_settings Public map config
GET, DELETE /listora/v1/settings/notifications/log Admin Settings_Controller::get_notification_log / clear_notification_log View/clear notification log
GET /listora/v1/settings/notifications/log/export Admin Settings_Controller::export_notification_log Download notification log as CSV/JSON for archiving
POST /listora/v1/settings/notifications/log/retention Admin Settings_Controller::set_notification_retention Set notification log retention policy (days)
POST /listora/v1/settings/notifications/test Admin Settings_Controller::send_test_notification Send test notification email
Method Route Auth Handler Purpose
GET /listora/v1/export/csv Admin Import_Export_Controller::export_csv Export listings CSV
POST /listora/v1/import/csv Admin Import_Export_Controller::import_csv Import CSV
POST /listora/v1/import/geojson Admin Import_Export_Controller::import_geojson Import GeoJSON with geo
POST /listora/v1/import/json Admin Import_Export_Controller::import_json Import JSON
POST /listora/v1/submit submit_listing_permissions Submission_Controller::submit_listing Frontend listing submission
POST /listora/v1/submit/check-duplicate logged_in_permissions Submission_Controller::check_duplicate_endpoint Pre-submit duplicate check
PUT /listora/v1/submit/{id} Owner Submission_Controller::edit_listing Owner edit listing

A member submits a listing they own to a BuddyNext space; the space team approves it before it appears in that space’s Businesses tab. These routes are the partner-facing API: BuddyNext owns spaces and space roles, so Listora never resolves a space_id itself and asks two filters instead (see the hooks reference). Both default to false, so with no BuddyNext installed every route here is closed.

Method Route Auth Handler Purpose
POST /listora/v1/listings/{id}/spaces Listing owner Space_Listings_Controller::submit Submit a listing you own to a space (lands as pending)
GET /listora/v1/spaces/{space_id}/listings wb_listora_user_can_view_space Space_Listings_Controller::showcase Approved listings for the space. Paginated: page, per_page (capped at 48), returns X-WP-Total / X-WP-TotalPages
GET /listora/v1/spaces/{space_id}/listings/pending wb_listora_user_can_moderate_space Space_Listings_Controller::pending Moderation queue, newest first Paginated: page, per_page (default 20, max 100), with X-WP-Total and X-WP-TotalPages.
POST /listora/v1/spaces/{space_id}/listings/{id}/approve wb_listora_user_can_moderate_space Space_Listings_Controller::approve Approve a pending submission
DELETE /listora/v1/spaces/{space_id}/listings/{id} Space team or the listing owner Space_Listings_Controller::remove Reject a submission, take an approved listing down, or withdraw your own

Known limits, so an integrator is not surprised:

  • …/listings/pending is paginated since 1.8.0 — page and per_page (default 20, max 100), with X-WP-Total and X-WP-TotalPages, the same headers the reviews and favourites routes send. Rows are ordered created_at DESC, listing_id DESC; the id tiebreaker matters, because several submissions in one second would otherwise sort arbitrarily and shuffle between pages.
  • There is no count endpoint. Space_Listings_Model::pending_count() exists but is not exposed, so a queue badge currently has to fetch the whole queue to show a number.
  • A member may hold at most 5 pending submissions per space by default; the 6th returns 429 with code listora_too_many_pending. Tune with wb_listora_space_pending_submission_limit.
  • DELETE fires the same action whether the team rejected the submission or the member withdrew it.


Section titled “Cookie + nonce (logged-in browser session)”

WordPress core localizes the REST nonce automatically. Read it from wp.apiFetch (when using @wordpress/api-fetch) or the page’s localized wpApiSettings.nonce:

// In a block's view.js (uses the apiFetch helper):
import apiFetch from '@wordpress/api-fetch';
const data = await apiFetch( { path: '/listora/v1/listings?per_page=12' } );
// Plain fetch with manual nonce:
const res = await fetch( '/wp-json/listora/v1/favorites', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.wpApiSettings.nonce },
body: JSON.stringify( { listing_id: 42 } )
} );
Terminal window
# Generate an Application Password under wp-admin → Users → Your Profile → Application Passwords
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{"title":"My Listing","type":"restaurant"}' \
https://yoursite.com/wp-json/listora/v1/submit

When wb-listora-pro is active, every route below registers under the same listora/v1 namespace and respects the same auth + nonce + rate-limit rules as Free. Permission per route is annotated as public (anyone), auth (logged-in only), cap:foo (requires that capability), or pro-toggle (feature toggle must be on at Settings → Features, where Pro toggles register since 1.1.0).

Method Route Permission Purpose
GET /needs public Public needs feed (with filters: type, location, status)
GET /needs/{id} public Single need detail
POST /needs auth Submit a new need
PUT /needs/{id} auth + owner Edit a need
DELETE /needs/{id} auth + owner Delete a need
GET /needs/matching/{listing_id} auth + listing owner Needs matching a listing’s type / location
GET /dashboard/needs auth “My Needs” + “My Responses” dashboard data
Method Route Permission Purpose
GET /credits/plans public Available pricing plans + entitlements
GET /credits/credit-packs public Credit packs available for purchase
POST /credits/purchase-plan auth Purchase / activate a plan (Hold-and-Commit flow)
POST /credits/admin-add cap:manage_listora_settings Manually grant credits to a user
Method Route Permission Purpose
POST /coupons/validate public Validate a coupon code (returns discount + eligibility)
POST /coupons/generate-code cap:manage_listora_settings Auto-generate a unique coupon code
Method Route Permission Purpose
GET /listings/{listing_id}/badges public Badges assigned to a listing
POST /listings/{listing_id}/badges/{badge_id} cap:manage_listora_settings Assign a badge
DELETE /listings/{listing_id}/badges/{badge_id} cap:manage_listora_settings Remove a badge
Method Route Permission Purpose
GET /webhooks cap:manage_listora_settings List configured outgoing webhooks
POST /webhooks cap:manage_listora_settings Create a webhook
GET /webhooks/{id} cap:manage_listora_settings Get webhook config
PUT /webhooks/{id} cap:manage_listora_settings Update webhook
DELETE /webhooks/{id} cap:manage_listora_settings Delete webhook
POST /webhooks/{id}/test cap:manage_listora_settings Fire a test payload
GET /webhooks/{id}/log cap:manage_listora_settings Recent delivery log
POST /webhooks/payment public + HMAC Inbound payment webhook receiver (Stripe + PayPal direct + WooCommerce / WooSubscriptions / MemberPress / PMPro / WooMemberships bridges)
Method Route Permission Purpose
GET /moderators cap:manage_listora_moderators List configured moderators
POST /moderators/{user_id}/activate cap:manage_listora_moderators Promote a WP user to moderator
POST /moderators/{user_id}/deactivate cap:manage_listora_moderators Demote a moderator
GET /moderators/{user_id}/queue moderator or above Items assigned to this moderator
POST /moderators/reassign cap:manage_listora_moderators Re-route queue items to a different moderator
GET /moderators/stats moderator or above Throughput / SLA stats per moderator
Method Route Permission Purpose
GET /migration/detect cap:manage_listora_settings Detect installed source plugin + row counts
GET /migration/fields cap:manage_listora_settings Source-field schema for the picked source
POST /migration/preview cap:manage_listora_settings Dry-run preview of N rows
POST /migration/run cap:manage_listora_settings Run the migration (batched, queueable)
Method Route Permission Purpose
GET /import/google/search cap:manage_listora_settings Google Places text search
GET /import/google/details cap:manage_listora_settings Get Place Details for a place_id
POST /import/google/import cap:manage_listora_settings Import a Place as a listing
GET /import/google/test cap:manage_listora_settings Smoke-test the API key
Method Route Permission Purpose
POST /import/upload cap:manage_listora_settings Upload a CSV / JSON file for visual mapping
GET /import/fields cap:manage_listora_settings Auto-detected source fields
POST /import/preview cap:manage_listora_settings Preview rows with the proposed mapping
POST /import/start cap:manage_listora_settings Kick off the batched import
GET /import/status/{batch_id} cap:manage_listora_settings Poll batch progress
POST /import/cancel/{batch_id} cap:manage_listora_settings Abort a running batch
GET /import/templates cap:manage_listora_settings Saved mapping templates
GET/PUT/DELETE /import/templates/{id} cap:manage_listora_settings Manage a saved template
Method Route Permission Purpose
GET /compare public Compare 2-4 listings side by side (returns merged data)
POST /compare/preview public Preview comparison set (used by Quick Compare modal)
Method Route Permission Purpose
GET /services/search public Cross-listing service search (“find SEO services near me”)
POST /services/compare public Compare service offerings across multiple listings
Method Route Permission Purpose
GET /audit-log cap:manage_listora_settings Recent audit entries (with filters)
GET /audit-log/export cap:manage_listora_settings CSV export of the current view
GET /audit-log/stats cap:manage_listora_settings Per-actor / per-event aggregates
Method Route Permission Purpose
GET /analytics/overview cap:manage_listora_settings Site-wide views / clicks / submissions over time
GET /analytics/listing/{id} listing owner or admin Per-listing analytics
POST /analytics/track public Beacon endpoint - record a view / phone-click / website-click
Method Route Permission Purpose
GET /saved-searches auth List my saved searches
POST /saved-searches auth Save the current search
PUT /saved-searches/{id} auth + owner Rename or update frequency
DELETE /saved-searches/{id} auth + owner Delete
Method Route Permission Purpose
POST /listings/{id}/contact public + nonce Pro lead form (replaces Free /contact-form when lead_form toggle is on)

Every endpoint that returns a resource also fires a wb_listora_rest_prepare_{resource} filter so Pro / themes / third-party code can inject custom fields without forking the controller:

add_filter( 'wb_listora_rest_prepare_listing', function ( $data, $post, $request ) {
$data['my_field'] = get_post_meta( $post->ID, '_my_field', true );
return $data;
}, 10, 3 );

See Hooks Reference → REST Response Filters for the full list of wb_listora_rest_prepare_* filters.

Public-write endpoints (POST /submissions, POST /listings/{id}/reviews, POST /claims, POST /listings/{id}/contact-form) are rate-limited per IP via sliding-window counters. Since 1.1.0, when WB Listora Pro is active its public read-only endpoints (credit packs, pricing plans, needs feed, comparisons, service search, badges) are also throttled - a per-IP cap of 60 requests/minute that fails open. See Rate Limiting & Abuse Controls for the default caps + per-endpoint windows + how to tune.