Acid Studios GET THE PLUGIN

Developers

Every filter and action with its signature and an example, the REST API under acid-aeo/v1, the WP-CLI commands, and the ai-context JSON schema.

Updated 2026-10-08 · applies to Acid AEO 1.0.4

Acid AEO has 13 filters and 2 actions, an administrative REST API and eight WP-CLI commands. All hook names start with acid_aeo_. The examples go in a small plugin or in your theme's functions.php.

Filters

acid_aeo_ai_context_data

Filters the site profile ai-context.json is built from. Parameters: array $data. Key pages sit under key_pages here and are published as keyPages.

add_filter( 'acid_aeo_ai_context_data', function ( $data ) {
	$data['key_pages'][] = [
		'title'   => 'Pricing',
		'url'     => home_url( '/pricing/' ),
		'summary' => 'Plans and what each one includes.',
	];
	return $data;
} );

acid_aeo_ai_txt_data

Filters the policy ai.txt is built from. Parameters: array $data. The contact key becomes the Contact line.

add_filter( 'acid_aeo_ai_txt_data', function ( $data ) {
	$data['contact'] = '[email protected]';
	return $data;
} );

acid_aeo_humans_data

Filters the colophon humans.txt is built from. Parameters: array $data. thanks is a list of names.

add_filter( 'acid_aeo_humans_data', function ( $data ) {
	$data['thanks'][] = 'The WordPress community';
	return $data;
} );

acid_aeo_indexnow_endpoint

Filters the IndexNow API endpoint. Parameters: string $endpoint, default https://api.indexnow.org/indexnow.

add_filter( 'acid_aeo_indexnow_endpoint', function ( $endpoint ) {
	return 'https://www.bing.com/indexnow';
} );

acid_aeo_is_noindex

Filters whether a post counts as noindex. Parameters: bool $noindex, int $post_id. A post that counts as noindex is left out of the generated files.

add_filter( 'acid_aeo_is_noindex', function ( $noindex, $post_id ) {
	if ( 'landing' === get_post_type( $post_id ) ) {
		return true;
	}
	return $noindex;
}, 10, 2 );

acid_aeo_last_updated_html

Filters the "Updated on" paragraph before it reaches the content. Parameters: string $html (already escaped), WP_Post $post. An empty string drops the line.

add_filter( 'acid_aeo_last_updated_html', function ( $html, $post ) {
	if ( has_category( 'news', $post ) ) {
		return '';
	}
	return $html;
}, 10, 2 );

acid_aeo_last_updated_threshold

Filters how far apart publishing and revising have to be, in seconds. Parameters: int $threshold (default DAY_IN_SECONDS), WP_Post $post. Zero makes every revision count.

add_filter( 'acid_aeo_last_updated_threshold', function ( $threshold, $post ) {
	return WEEK_IN_SECONDS;
}, 10, 2 );

acid_aeo_llms_data

Filters the site summary llms.txt and llms-full.txt are built from. Parameters: array $data. sections is a list of title and items; the first section is Key pages.

add_filter( 'acid_aeo_llms_data', function ( $data ) {
	$data['sections'][0]['items'][] = [
		'title'   => 'Pricing',
		'url'     => home_url( '/pricing/' ),
		'summary' => 'Plans and what each one includes.',
	];
	return $data;
} );

acid_aeo_llms_full_documents

Filters how many documents llms-full.txt considers before the byte budget (llms.full_max_bytes) applies. Parameters: int $limit, default 200. Key pages are added on top; the result is at least 1.

add_filter( 'acid_aeo_llms_full_documents', function ( $limit ) {
	return 500;
} );

acid_aeo_loopback_args

Filters the arguments of an audit loopback request, for a host that needs a longer timeout, an extra header or basic auth to answer its own requests. Parameters: array $args (for wp_remote_get()), string $url.

add_filter( 'acid_aeo_loopback_args', function ( $args, $url ) {
	$args['headers']['Authorization'] = 'Basic ' . base64_encode( 'staging:secret' );
	$args['timeout'] = 20;
	return $args;
}, 10, 2 );

acid_aeo_schema_context

Filters the facts the JSON-LD graph is built from. Parameters: array $ctx, with the keys site, page and pieces. pieces maps each piece (organization, website, webpage, breadcrumb, article, person, faq, speakable, image_meta) to true or false.

add_filter( 'acid_aeo_schema_context', function ( $ctx ) {
	if ( is_page( 'contact' ) ) {
		$ctx['pieces']['speakable'] = false;
	}
	return $ctx;
} );

acid_aeo_schema_graph

Filters the finished JSON-LD graph. Parameters: array $graph, holding @context and @graph, or empty when there is nothing to print. Returning an empty array prints nothing.

add_filter( 'acid_aeo_schema_graph', function ( $graph ) {
	if ( isset( $graph['@graph'] ) && is_front_page() ) {
		$graph['@graph'][] = [
			'@type'    => 'Service',
			'name'     => 'Website audits',
			'provider' => [ '@id' => home_url( '/#organization' ) ], // a personal site uses /#person
		];
	}
	return $graph;
} );

acid_aeo_security_data

Filters the contact details security.txt is built from. Parameters: array $data. contact is a list; a bare email address becomes a mailto: URI.

add_filter( 'acid_aeo_security_data', function ( $data ) {
	$data['contact'][] = 'https://example.com/security/';
	return $data;
} );

Actions

acid_aeo_daily_tasks fires once a day, from the acid_aeo_daily cron event scheduled on activation. No parameters. The plugin itself uses it to prune old citation events, renew the security.txt expiry date and rewrite the static files.

add_action( 'acid_aeo_daily_tasks', function () {
	delete_transient( 'my_llms_report' );
} );

acid_aeo_settings_updated fires after settings are saved. Parameters: string $section, the section that changed, or * after an import replaced all of them. It fires for saves from the settings screen, each wizard step and the audit's fixes. A single value the plugin writes internally (the wizard's progress, a key generated on boot) does not fire it; the audit fix for the key does.

add_action( 'acid_aeo_settings_updated', function ( $section ) {
	if ( in_array( $section, [ 'llms', 'identity', '*' ], true ) ) {
		my_cdn_purge( [ '/llms.txt', '/llms-full.txt' ] );
	}
} );

REST API

Base: /wp-json/acid-aeo/v1/. Every route below requires the manage_options capability. From the admin, send a wp_rest nonce in the X-WP-Nonce header; from outside, authenticate with an application password.

Route Method What it does
/settings GET, PUT Read every setting; save one section with data, or a sections object
/environment GET What the plugin detected about the site
/preview/{file} GET Render a file: llms, llms_full, robots, humans, ai_txt, ai_context, security; optional draft
/audit GET Run the readiness audit; fresh=1 skips the cached result
/audit/fix/{id} POST Apply one safe fix, then re-run the audit
/wizard/step POST Save one wizard step (step, data)
/wizard/complete POST Finish setup; submit_indexnow sends every URL
/indexnow/submit POST Submit urls, or all=true for the whole site; 409 while the module is off
/indexnow/log GET The last 50 submissions
/citations/stats GET Totals, agents, top pages and timeline for days (1–365, default 30)
/citations/events GET Events, newest first: page, per_page (max 200), type (crawl or referral)
/citations/export GET Every event as a CSV download
/citations DELETE Delete events; older_than_days keeps the newer ones
/static-files/write POST Write the static files now
/cache/flush POST Drop every cached file
/export GET Every setting, as JSON
/import POST Replace every setting with a settings object
/system-info GET Versions and environment for a support request

The one public route, POST /beacon, belongs to the browser beacon of the citations module and is not meant for you to call. See Privacy.

curl -u admin:"abcd efgh ijkl mnop qrst uvwx" \
  "https://example.com/wp-json/acid-aeo/v1/audit?fresh=1"

curl -u admin:"abcd efgh ijkl mnop qrst uvwx" -X POST \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://example.com/pricing/"]}' \
  https://example.com/wp-json/acid-aeo/v1/indexnow/submit

WP-CLI

wp acid-aeo audit [--fresh] [--format=<table|json>]
wp acid-aeo regenerate
wp acid-aeo indexnow <submit|log> [<url>...] [--all]
wp acid-aeo static <write|remove>
wp acid-aeo export [--file=<path>]
wp acid-aeo import <file>
wp acid-aeo citations <stats|purge> [--days=<days>] [--older-than=<days>]
wp acid-aeo preview <llms|llms_full|robots|humans|ai_txt|ai_context|security>
  • audit prints the findings and exits with status 1 when the score is below 50, so a deployment can gate on it. --format defaults to table.
  • regenerate drops every cached file and rebuilds the rewrite rules.
  • indexnow submit sends the URLs given; --all queues every published post of the configured types; exits 1 with a message while the module is off. log prints the history.
  • static write writes the generated files to disk; remove deletes the ones the plugin wrote. robots.txt is never touched.
  • export prints every setting as JSON, or writes it to --file.
  • import replaces every setting with an export file, sanitized on the way in.
  • citations stats reports on the last --days (default 30); purge deletes events older than --older-than, or all of them.
  • preview prints what a file would say now, even with its module off.

The ai-context schema

ai-context.json names its schema in $schema: https://acidstudios.ro/schemas/ai-context/1.0.json. The plugin writes that URL and never fetches it.

The twelve top-level members are always present, in this order: $schema, version (1.0), generated, language, instructions, organization, offerings, keyPages, faq, contact, policy and links. Empty values are dropped at every depth; an empty object section is {} and an empty list is []. The instructions.generator field (Acid AEO and the version) is how the plugin recognises a copy it wrote itself.

Settings paths

All settings live in one option, acid_aeo_settings, addressed by dotted paths such as identity.name. The sections are general, identity, policy, llms, offerings, faq, humans, security, ai_context, ai_txt, schema, head, headers, indexnow, citations, content and static_files; the section name is what acid_aeo_settings_updated receives. The wizard page lists the paths each step writes. The ones most often read:

Path Default
general.site_type business
identity.name, identity.email empty
policy.preset citable
llms.key_pages empty list of post IDs
llms.posts_limit 20
llms.full_max_bytes 2000000
schema.defer_to_seo on
indexnow.enabled, indexnow.auto_submit on
citations.beacon off
citations.retention_days 90
content.last_updated off
static_files.enabled off

Every module's switch is {section}.enabled. To read a value in PHP, use \AcidStudios\AEO\Options::get( 'identity.name' ).

Found a mistake or something missing? Write to [email protected].

GET THE PLUGIN