> For the complete documentation index, see [llms.txt](https://docs.acpt.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.acpt.io/integrations/abilities-api/how-it-works.md).

# How it works

Ability naming, categories, the response envelope, permissions, and error handling

Every ACPT ability follows the same shape. Understanding this once means you can predict the request/response contract of all 57 abilities without reading each one individually.

## Naming convention

All ability names use ACPT's `acpt/` namespace, followed by a lowercase, hyphen-separated verb-noun pair — matching the [Abilities API naming rules](https://developer.wordpress.org/apis/abilities-api/) (lowercase alphanumeric and hyphens only, no underscores):

```
acpt/list-post-types
acpt/get-post-type
acpt/save-post-type
acpt/delete-post-type
```

Most domains expose the same four operations — **list**, **get**, **save**, **delete** — with two exceptions:

* **Taxonomies** add two extra abilities to attach/detach a taxonomy from a post type: `acpt/assoc-taxonomy-post-type` and `acpt/remove-taxonomy-post-type-assoc`.
* **Form fields** have no `list` ability, since `acpt/get-form` already returns a form's fields array — a separate listing ability would just duplicate it.

{% hint style="info" %}
There's no `create`/`update` split. Every "save" ability is an **upsert**: it creates the item if it doesn't exist yet, or updates it in place if an item with the same natural key (post type slug, taxonomy slug, field name, form name, etc.) already exists. This mirrors ACPT's own PHP function API — see [Functions](/developers/functions.md) — where `register_acpt_post_type()`, `save_acpt_form()`, and friends all behave the same way.
{% endhint %}

## Categories

Abilities are grouped into two categories, registered on WordPress's `wp_abilities_api_categories_init` hook:

| Category slug        | Used for                                                                                                                                             |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `acpt-content-model` | Defining your content **structure**: custom post types, taxonomies, meta field groups/boxes/fields, option pages, forms, form fields, dynamic blocks |
| `acpt-content-data`  | Reading and writing **content values**: field values on posts, terms, users, comments and option pages                                               |

The split matters because content-model abilities default to a stricter capability (`manage_options`) than content-data abilities (`edit_posts`) — see [Permissions](#permissions) below.

## The response envelope

Every ability's `execute_callback` returns one of two things:

**On success**, a plain array shaped like this (validated against the ability's `output_schema`):

```json
{
  "success": true,
  "message": "Operation completed successfully.",
  "data": { }
}
```

* `data` holds whatever the operation returns — a single object for `get`/`save` abilities, an array for `list` abilities, or is simply absent for `delete` abilities and abilities that don't return a resource (e.g. `acpt/assoc-taxonomy-post-type`).
* Objects and arrays returned by ACPT's internal models are normalized into plain JSON-friendly data before being placed in `data` — you never receive a raw PHP object or a `WP_Post_Type` instance.

{% hint style="info" %}
Each per-domain page documents the fields that are part of the stable, guaranteed response shape. In practice, `data` objects for meta field groups/boxes/fields, forms, dynamic blocks, and option pages may also include a handful of additional ACPT-internal bookkeeping fields (things like sort order, denormalized parent references, or computed counts) beyond what's documented — these aren't part of the guaranteed contract and may change between releases, so don't build logic against fields not listed on the relevant page.
{% endhint %}

**On failure**, a `WP_Error` instead of an array — this is standard Abilities API / WordPress convention, so any Abilities API consumer already knows how to handle it:

```php
$result = $ability->execute($input);

if (is_wp_error($result)) {
    $result->get_error_code();    // e.g. "acpt_ability_failed"
    $result->get_error_message(); // a human-readable explanation
}
```

Two error codes are used across all ACPT abilities:

| Code                     | Meaning                                                                                                                                                                                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `acpt_ability_failed`    | The underlying ACPT operation failed — invalid input, item not found, or a validation error. The message is ACPT's own error message where available, otherwise a generic per-ability fallback (e.g. *"Unable to save the custom post type."*). |
| `acpt_ability_forbidden` | The current user doesn't have the required capability. See [Permissions](#permissions).                                                                                                                                                         |

{% hint style="info" %}
If the input you send doesn't validate against the ability's `input_schema` at all, WordPress core rejects the call before ACPT's code ever runs and returns its own `WP_Error` — this is handled entirely by the Abilities API, not by ACPT.
{% endhint %}

## Permissions

Every ability has a `permission_callback` that checks a WordPress **capability** with `current_user_can()`:

* Abilities in the `acpt-content-model` category default to **`manage_options`**.
* Abilities in the `acpt-content-data` category (field values) default to **`edit_posts`** — a lower bar, since reading/writing field *values* is closer to everyday content editing than redesigning the content model.

Both defaults can be overridden — per-ability, per-user, or based on the input being sent — via the `acpt/abilities-api/capability` filter. See [Extending](/integrations/abilities-api/extending.md#overriding-the-required-capability).

{% hint style="warning" %}
ACPT's own per-entity `permissions` arrays (the role-based rules you can set when building a post type, taxonomy, etc. in the ACPT UI) are **not** consulted by these abilities in the current version — only the single capability check described above applies. Honoring per-entity permissions inside `permission_callback` is a documented improvement for a future release.
{% endhint %}

## Read/write/destructive metadata

Every registered ability also carries standard Abilities API annotations in its `meta`, so AI agents and MCP clients can reason about risk before calling something:

| Annotation     | Value                                                                                                                                                        |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `readonly`     | `true` for `list`/`get` abilities, `false` otherwise                                                                                                         |
| `destructive`  | `true` for `delete` abilities (and `acpt/remove-taxonomy-post-type-assoc`), `false` otherwise                                                                |
| `idempotent`   | `true` for every ACPT ability — calling the same `save` twice with the same input produces the same end state, thanks to the upsert behavior described above |
| `show_in_rest` | `true` for every ACPT ability                                                                                                                                |

## What actually gets called

Each ability wraps an existing, already-tested piece of ACPT: either one of the documented [PHP functions](/developers/functions.md) (`register_acpt_post_type()`, `save_acpt_meta_group()`, `get_acpt_field()`, …) or, where no such function exists (e.g. listing all post types), the same repository classes ACPT's admin UI uses internally. No business logic is duplicated — the Abilities API integration is purely a thin, schema-validated wrapper around ACPT's existing API surface.
