API & CLI
REST API endpoints, helper functions, WP-CLI commands, and the module system for building add-ons.
REST API
Endpoints
All endpoints use namespace apd/v1. The base URL is /wp-json/apd/v1/.
Listings
Taxonomies
Favorites
Reviews
Inquiries
The full permissions matrix:
| Endpoint | Methods | Auth Required | Ownership / Capability | Nonce Required | Typical Error Codes |
|---|---|---|---|---|---|
/listings | GET | No | — | No | — |
/listings | POST | Yes | — | Yes | 401, 403 |
/listings/{id} | GET | No | — | No | — |
/listings/{id} | PUT | Yes | Owner or edit_others_posts | Yes | 401, 403 |
/listings/{id} | DELETE | Yes | Owner or delete_others_posts | Yes | 401, 403 |
/categories | GET | No | — | No | — |
/categories/{id} | GET | No | — | No | — |
/tags | GET | No | — | No | — |
/tags/{id} | GET | No | — | No | — |
/favorites | GET | Yes | — | No | 401 |
/favorites | POST | Yes | — | Yes | 401, 403 |
/favorites/{id} | DELETE | Yes | — | Yes | 401, 403 |
/favorites/listings | GET | Yes | — | No | 401 |
/favorites/toggle/{id} | POST | Yes | — | Yes | 401, 403 |
/reviews | GET | No | — | No | — |
/reviews | POST | Yes | — | Yes | 401, 403 |
/reviews/{id} | GET | No | — | No | — |
/reviews/{id} | PUT | Yes | Author | Yes | 401, 403 |
/reviews/{id} | DELETE | Yes | Author or moderate_comments | Yes | 401, 403 |
/listings/{id}/reviews | GET | No | — | No | — |
/inquiries | GET | Yes | — | No | 401 |
/inquiries/{id} | GET | Yes | Listing owner | No | 401, 403 |
/inquiries/{id} | DELETE | Yes | Listing owner | Yes | 401, 403 |
/inquiries/{id}/read | POST | Yes | Listing owner | Yes | 401, 403 |
/inquiries/{id}/unread | POST | Yes | Listing owner | Yes | 401, 403 |
/listings/{id}/inquiries | GET | Yes | Listing owner | No | 401, 403 |
Authentication
What's required depends on the endpoint and HTTP method:
- Public GET endpoints (listings, categories, tags, listing reviews) — no authentication needed.
- Authenticated endpoints (favorites, inquiries, user-specific data) — require a logged-in user.
- All write endpoints (POST, PUT, DELETE) — require a valid
X-WP-Nonceheader with thewp_restnonce value.
Include the nonce header in all authenticated and write requests:
fetch( '/wp-json/apd/v1/favorites', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce,
},
body: JSON.stringify( { listing_id: 42 } ),
} );
cURL Examples
# Public GET — no authentication needed
curl https://example.com/wp-json/apd/v1/listings
# Authenticated GET — pass WordPress auth cookie
curl https://example.com/wp-json/apd/v1/favorites \
-b wordpress_logged_in_xxx=COOKIE_VALUE
# State-changing POST — cookie + X-WP-Nonce required
curl -X POST https://example.com/wp-json/apd/v1/favorites \
-b wordpress_logged_in_xxx=COOKIE_VALUE \
-H "X-WP-Nonce: NONCE_VALUE" \
-H "Content-Type: application/json" \
-d '{"listing_id": 42}'
To obtain a nonce for cURL testing, call /wp-json/apd/v1/ with a valid cookie and read the X-WP-Nonce header from the response, or generate one via WP-CLI: wp eval 'echo wp_create_nonce("wp_rest");'
Error Responses
Missing or invalid authentication returns standard WordPress REST API error codes:
| HTTP Status | Error Code | Meaning |
|---|---|---|
| 401 | rest_not_logged_in | The request requires an authenticated user but no user session was found. |
| 403 | rest_nonce_invalid | The X-WP-Nonce header is missing, expired, or invalid. |
| 403 | rest_forbidden | The user does not have the required capability or ownership for this resource. |
Adding Custom Endpoints
Hook into apd_register_rest_routes to add your own endpoints under the apd/v1 namespace. Use permission_authenticated_with_nonce for write endpoints and permission_authenticated for read-only private endpoints:
add_action( 'apd_register_rest_routes', function( $controller ) {
// Read-only private endpoint (auth required, no nonce)
register_rest_route( 'apd/v1', '/custom', [
'methods' => 'GET',
'callback' => 'my_custom_read',
'permission_callback' => [ $controller, 'permission_authenticated' ],
] );
// Write endpoint (auth + nonce required)
register_rest_route( 'apd/v1', '/custom', [
'methods' => 'POST',
'callback' => 'my_custom_write',
'permission_callback' => [ $controller, 'permission_authenticated_with_nonce' ],
] );
} );
function my_custom_read( WP_REST_Request $request ) {
return apd_rest_response( [ 'data' => 'value' ] );
}
function my_custom_write( WP_REST_Request $request ) {
// Safe to mutate state — nonce already verified
return apd_rest_response( [ 'created' => true ], 201 );
}
Response Helpers
APD provides helper functions for building consistent API responses:
// Success response
return apd_rest_response( $data, 200, $headers );
// Error response
return apd_rest_error( 'error_code', 'Error message', 400 );
// Paginated response
return apd_rest_paginated_response( $items, $total, $page, $per_page );
apd_rest_response( $data, $status, $headers )— Returns aWP_REST_Responsewith the given data, status code (default 200), and optional headers.apd_rest_error( $code, $message, $status )— Returns aWP_Errorwith the specified error code, message, and HTTP status.apd_rest_paginated_response( $items, $total, $page, $per_page )— Returns a paginated response withX-WP-TotalandX-WP-TotalPagesheaders.
Functions
Helper Functions
Global helper functions available in your theme and plugins. All are prefixed with apd_.
Listings
apd_get_listing_field( $listing_id, $field_name, $default );
apd_set_listing_field( $listing_id, $field_name, $value );
apd_get_listing_views( $listing_id );
apd_increment_listing_views( $listing_id );
apd_get_related_listings( $listing_id, $limit, $args );
Taxonomies
apd_get_listing_categories( $listing_id );
apd_get_listing_tags( $listing_id );
apd_get_category_listings( $category_id, $args );
apd_get_categories_with_count( $args );
apd_get_category_icon( $category );
apd_get_category_color( $category );
Favorites
apd_add_favorite( $listing_id, $user_id );
apd_remove_favorite( $listing_id, $user_id );
apd_toggle_favorite( $listing_id, $user_id );
apd_is_favorite( $listing_id, $user_id );
apd_get_user_favorites( $user_id );
apd_get_favorites_count( $user_id );
apd_get_listing_favorites_count( $listing_id );
Reviews
apd_get_listing_reviews( $listing_id, $args );
apd_get_listing_rating( $listing_id );
apd_get_listing_review_count( $listing_id );
apd_create_review( $listing_id, $data );
apd_update_review( $review_id, $data );
apd_delete_review( $review_id );
apd_current_user_has_reviewed( $listing_id );
Settings
apd_get_setting( $key, $default );
apd_set_setting( $key, $value );
apd_get_all_settings();
apd_reviews_enabled();
apd_favorites_enabled();
apd_contact_form_enabled();
apd_format_price( $amount );
Templates
apd_get_template( $template, $args );
apd_get_template_part( $slug, $name, $args );
apd_get_template_html( $template, $args );
apd_template_exists( $template );
apd_locate_template( $template );
Caching
apd_cache_remember( $key, $callback, $expiration );
apd_cache_get( $key );
apd_cache_set( $key, $value, $expiration );
apd_cache_delete( $key );
apd_cache_clear_all();
CLI
WP-CLI Commands
APD includes WP-CLI commands for managing demo data without the browser.
# Generate all demo data with default quantities
wp apd demo generate
# Generate with custom quantities
wp apd demo generate --users=10 --listings=50
# Generate only specific data types
wp apd demo generate --types=categories,tags,listings
# Show current demo data counts
wp apd demo status
# Show counts as JSON
wp apd demo status --format=json
# Delete all demo data (with confirmation)
wp apd demo delete
# Delete without confirmation prompt
wp apd demo delete --yes
Generate Options
| Option | Default | Description |
|---|---|---|
--types=<types> | all | Comma-separated list of data types: users, categories, tags, listings, reviews, inquiries, favorites, all |
--users=<count> | 5 | Number of demo users to create (max 20) |
--tags=<count> | 10 | Number of tags to create (max 10) |
--listings=<count> | 25 | Number of listings to create (max 100) |
Status Options
| Option | Default | Description |
|---|---|---|
--format=<format> | table | Output format: table, json, csv, yaml |
Delete Options
| Option | Description |
|---|---|
--yes | Skip the confirmation prompt |
Notes
- Reviews require listings to exist (2–4 per listing, automatically generated).
- Inquiries require listings (0–2 per listing, random).
- Favorites require both listings and users.
- Module providers (from external modules) are automatically included in generate, delete, and status commands.
- All demo data is tracked with the
_apd_demo_datameta key for clean removal.
Modules
Module System
External plugins can register themselves as APD modules via the apd_modules_init action hook, which fires at init priority 1.
Module Configuration
Each module is defined by a configuration array with these properties:
| Property | Type | Default | Description |
|---|---|---|---|
name | string | — | Module display name (required) |
description | string | '' | Short description of the module |
version | string | '1.0.0' | Module version number |
author | string | '' | Module author name |
requires | array | [] | Dependencies, e.g. [ 'core' => '1.0.0' ] |
features | array | [] | Feature flags, e.g. [ 'link_checker', 'favicon_fetcher' ] |
hidden_fields | array | [] | Core fields to hide when module is active |
icon | string | 'dashicons-admin-plugins' | Dashicon class for admin UI |
priority | int | 10 | Load order priority (lower loads first) |
Registration Functions
// Register a module with array configuration
apd_register_module( $slug, $config );
// Register a class-based module (implements ModuleInterface)
apd_register_module_class( $module );
// Unregister a module
apd_unregister_module( $slug );
// Get a registered module by slug
apd_get_module( $slug );
// Get all registered modules (supports orderby, order, feature args)
apd_get_modules( $args );
// Check if a module is registered
apd_has_module( $slug );
// Get modules that support a specific feature
apd_get_modules_by_feature( $feature );
// Check if module requirements are met
apd_module_requirements_met( $requires );
Developing Modules
To create an APD module, build a standard WordPress plugin that hooks into apd_modules_init. Here is a minimal example:
/**
* Plugin Name: My APD Module
* Description: Custom module for DamDir Directory.
* Requires Plugins: damdir-directory
*/
add_action( 'apd_modules_init', function() {
apd_register_module( 'my-module', [
'name' => 'My Module',
'description' => 'Adds custom functionality to APD.',
'version' => '1.0.0',
'author' => 'Your Name',
'requires' => [ 'core' => '1.0.0' ],
'features' => [ 'my_feature' ],
'icon' => 'dashicons-admin-generic',
] );
} );
For more complex modules, implement ModuleInterface and use class-based registration:
use APD\Module\ModuleInterface;
class My_Module implements ModuleInterface {
public function get_slug(): string {
return 'my-module';
}
public function get_name(): string {
return 'My Module';
}
public function get_description(): string {
return 'Adds custom functionality to APD.';
}
public function get_version(): string {
return '1.0.0';
}
public function get_config(): array {
return [
'requires' => [ 'core' => '1.0.0' ],
'features' => [ 'my_feature' ],
'icon' => 'dashicons-admin-generic',
];
}
public function init(): void {
// Hook into WordPress and APD actions/filters here.
}
}
add_action( 'apd_modules_init', function() {
apd_register_module_class( new My_Module() );
} );
Things to keep in mind:
- Always declare
'core' => '1.0.0'(or higher) inrequiresto ensure compatibility. - Use the
featuresarray to advertise capabilities — other modules can query by feature withapd_get_modules_by_feature(). - The
hidden_fieldsproperty lets your module hide core fields that it replaces or overrides. - Use
apd_module_requirements_met()to verify dependencies before initializing. - Hook into
apd_modules_loadedif you need to run code after all modules have registered. - Module registration, unregistration, and retrieval can be modified with the
apd_register_module_config,apd_module_registered,apd_module_unregistered,apd_get_module, andapd_get_moduleshooks.