A custom WordPress Abilities API script integrating Independent Analytics. Allows AI agents to query top posts, single page stats, and all-time database views.
Of course this is only the ability you need an agent to use this ability with it.

/**
* Unified Analytics Ability (Updated with Custom Post Type instructions)
* Registers the snn/get-analytics ability for the WordPress Abilities API
*/
// Register ability
add_action( 'wp_abilities_api_init', 'snn_register_unified_analytics_ability' );
function snn_register_unified_analytics_ability() {
wp_register_ability(
'snn/get-analytics',
array(
'label' => __( 'Get Independent Analytics', 'wp-abilities' ),
'description' => __( 'Retrieves traffic analytics. Use "top_all_time" for absolute total historical views. Use "top" to get a leaderboard for a specific date range. Use "single" to check a specific post ID. CRITICAL OUTPUT RULE: When returning a list of top posts to the user, you MUST list out every single item you fetched. Do not summarize or skip items.', 'wp-abilities' ),
'category' => 'seo',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'query_type' => array(
'type' => 'string',
'description' => 'Choose "top_all_time" for all-time total views, "top" for date-range leaderboards, or "single" for a specific post.',
'enum' => array( 'top', 'top_all_time', 'single' ),
'default' => 'top_all_time',
),
'post_id' => array(
'type' => 'integer',
'description' => 'Required if query_type is "single". The ID of the post.',
),
'limit' => array(
'type' => 'integer',
'description' => 'Used if query_type is "top" or "top_all_time". Max number of posts to fetch (1 to 100).',
'default' => 10,
),
'sort_by' => array(
'type' => 'string',
'description' => 'Used if query_type is "top". Acceptable values: views, visitors, sessions.',
'enum' => array( 'views', 'visitors', 'sessions' ),
'default' => 'views',
),
'post_type' => array(
'type' => 'string',
'description' => 'CRITICAL: The post type to query. Defaults to "post". If the user asks for "pages", "codex", "products", or any other specific content type, you MUST change this value to match that specific custom post type slug (e.g., "page", "codex", "product").',
'default' => 'post',
),
'category' => array(
'type' => 'integer',
'description' => 'Optional category ID to filter by if query_type is "top" or "top_all_time".',
),
'from' => array(
'type' => 'string',
'description' => 'Start date (e.g., "-30 days"). Only applies if query_type is "top" or "single".',
'default' => '-30 days',
),
'to' => array(
'type' => 'string',
'description' => 'End date (e.g., "today"). Only applies if query_type is "top" or "single".',
'default' => 'today',
),
),
'required' => array( 'query_type' ),
),
'output_schema' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer', 'description' => 'Post ID' ),
'title' => array( 'type' => 'string', 'description' => 'Post Title' ),
'views' => array( 'type' => 'integer', 'description' => 'Total views' ),
'visitors' => array( 'type' => 'integer', 'description' => 'Unique visitors (May be 0 for all-time queries)' ),
'sessions' => array( 'type' => 'integer', 'description' => 'Total sessions (May be 0 for all-time queries)' ),
),
),
),
'execute_callback' => function( $input ) {
$query_type = isset( $input['query_type'] ) ? $input['query_type'] : 'top_all_time';
$result = array();
// --- ALL-TIME TOP POSTS LOGIC ---
if ( $query_type === 'top_all_time' ) {
$limit = isset( $input['limit'] ) ? min( absint( $input['limit'] ), 100 ) : 10;
$post_type = isset( $input['post_type'] ) ? sanitize_text_field( $input['post_type'] ) : 'post';
$args = array(
'post_type' => $post_type,
'posts_per_page' => $limit,
'meta_key' => 'iawp_total_views',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'post_status' => 'publish',
);
if ( ! empty( $input['category'] ) ) {
$args['cat'] = absint( $input['category'] );
}
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$result[] = array(
'id' => get_the_ID(),
'title' => get_the_title(),
'views' => (int) get_post_meta( get_the_ID(), 'iawp_total_views', true ),
'visitors' => 0, // Meta field only tracks views, not visitors
'sessions' => 0, // Meta field only tracks views, not sessions
);
}
}
wp_reset_postdata();
return $result;
}
// Ensure the Independent Analytics API is available for the date-based queries
if ( ! function_exists( 'iawp_top_posts' ) || ! function_exists( 'iawp_singular_analytics' ) ) {
return new WP_Error( 'missing_plugin', __( 'Independent Analytics Developer API is unavailable.', 'wp-abilities' ), array( 'status' => 500 ) );
}
// Safely parse dates for 'top' and 'single' queries
try {
$from_date = new DateTime( isset( $input['from'] ) ? $input['from'] : '-30 days' );
$to_date = new DateTime( isset( $input['to'] ) ? $input['to'] : 'today' );
} catch ( Exception $e ) {
return new WP_Error( 'invalid_date', __( 'Invalid date format.', 'wp-abilities' ), array( 'status' => 400 ) );
}
// --- SINGLE POST LOGIC ---
if ( $query_type === 'single' ) {
if ( empty( $input['post_id'] ) ) {
return new WP_Error( 'missing_id', __( 'post_id is required when query_type is single.', 'wp-abilities' ), array( 'status' => 400 ) );
}
$post_id = absint( $input['post_id'] );
$analytics = iawp_singular_analytics( $post_id, $from_date, $to_date );
$result[] = array(
'id' => $post_id,
'title' => get_the_title( $post_id ),
'views' => isset( $analytics->views ) ? (int) $analytics->views : 0,
'visitors' => isset( $analytics->visitors ) ? (int) $analytics->visitors : 0,
'sessions' => isset( $analytics->sessions ) ? (int) $analytics->sessions : 0,
);
// --- DATE-RANGE TOP POSTS LOGIC ---
} elseif ( $query_type === 'top' ) {
$args = array(
'post_type' => isset( $input['post_type'] ) ? sanitize_text_field( $input['post_type'] ) : 'post',
'limit' => isset( $input['limit'] ) ? min( absint( $input['limit'] ), 100 ) : 10,
'from' => $from_date,
'to' => $to_date,
'sort_by' => isset( $input['sort_by'] ) ? sanitize_text_field( $input['sort_by'] ) : 'views',
);
if ( ! empty( $input['category'] ) ) {
$args['category'] = absint( $input['category'] );
}
$top_posts = iawp_top_posts( $args );
if ( is_array( $top_posts ) ) {
foreach ( $top_posts as $post ) {
$result[] = array(
'id' => isset( $post->id ) ? (int) $post->id : 0,
'title' => isset( $post->title ) ? (string) $post->title : '',
'views' => isset( $post->views ) ? (int) $post->views : 0,
'visitors' => isset( $post->visitors ) ? (int) $post->visitors : 0,
'sessions' => isset( $post->sessions ) ? (int) $post->sessions : 0,
);
}
}
}
return $result;
},
'permission_callback' => '__return_true',
'meta' => array(
'show_in_rest' => true,
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
)
);
}