# auth.md

> Authentication guide for AI agents that want to manage WPSubscription
> subscriptions programmatically. This describes the **real** auth model: a
> per-site API key. There is no OAuth server and no automated agent registration.

## Audience

This document is for AI agents acting **on behalf of a WordPress site owner** who
runs the [WPSubscription](https://wpsubscription.co) plugin and wants to automate
subscription actions (cancel, pause, resume, reactivate, expire, change status).

## Where the API lives

The WPSubscription REST API runs on **each customer's own WordPress site**, not on
`wpsubscription.co`. This domain only publishes discovery and documentation:

- OpenAPI spec: <https://wpsubscription.co/.well-known/openapi.json>
- Agent skill: <https://wpsubscription.co/.well-known/agent-skills/wpsubscription-api/SKILL.md>
- Human docs: <https://docs.wpsubscription.co/en/wpsubscription-rest-api-integration>

## Authentication method

| Property | Value |
|----------|-------|
| Method | API key |
| Transport | `api_key` field in the JSON request body (not an HTTP header) |
| OAuth / OIDC | Not used |
| Dynamic client registration | Not supported |
| Bearer tokens | Not used |

## Credential provisioning (registration)

There is **no automated registration endpoint** — credentials are provisioned by a
human, not minted on demand:

1. The WordPress site owner opens **WP Admin → WPSubscription → Settings →
   API Settings** and enables *REST API endpoints for subscription actions*.
2. They generate an API key for that site.
3. They share the key with the agent (or its operator) over a trusted,
   out-of-band channel.

Agents must **not** attempt to self-register or probe for credential endpoints.

## Scopes and permissions

API keys are **least-privilege by construction**. Each key is scoped two ways:

1. **Site-scoped** — a key is valid only on the one WordPress site that issued
   it. It cannot be used against any other site.
2. **Capability-scoped** — a key can only perform the subscription lifecycle
   actions below. It grants **no** access to WordPress users, posts, orders,
   settings, or any other admin capability.

| Scope | Action value | What it permits |
|-------|--------------|-----------------|
| `subscription:cancel` | `cancel_subscription` | Cancel an active subscription |
| `subscription:pause` | `pause_subscription` | Put a subscription on hold |
| `subscription:resume` | `resume_subscription` | Reactivate a paused subscription |
| `subscription:reactivate` | `reactivate_subscription` | Reactivate a cancelled subscription |
| `subscription:expire` | `expire_subscription` | Mark a subscription as expired |
| `subscription:change_status` | `change_status` | Set an explicit status (`active`, `pending`, `cancelled`, `expired`) |
| `subscription:trial_end` | `trial_end` | Trigger end-of-trial logic |
| `subscription:payment_failed` | `payment_failed` | Log or react to a failed renewal |

There is no per-key scope selection UI: every key carries exactly this fixed set,
nothing more. The same list is machine-readable in the OpenAPI spec under
`x-scoped-permissions`.

## Using the credential

Send the key in the body of every request:

```http
POST https://{your-wordpress-site}/wp-json/wpsubscription/v1/action
Content-Type: application/json

{
  "api_key": "<your-api-key>",
  "action": "pause_subscription",
  "subscription_id": "123"
}
```

To check a key without changing anything, send a request with a
`subscription_id` that does not exist: `not_found` means the key is valid,
`rest_forbidden` means it is not. See the
[OpenAPI spec](https://wpsubscription.co/.well-known/openapi.json) for the full
request and response schema.

## Errors

Every error is structured JSON in the standard WordPress REST shape — a
machine-readable `code`, a human-readable `message`, and `data.status` mirroring
the HTTP status. Branch on `code`, not on message text:

```json
{
  "code": "rest_forbidden",
  "message": "Invalid API key.",
  "data": { "status": 403 }
}
```

| Code | Meaning | Resolution |
|------|---------|------------|
| `rest_forbidden` | Invalid or missing API key | Regenerate the key in WP Admin → WPSubscription → Settings |
| `not_found` | Subscription ID not found | Verify the ID exists on this site |
| `invalid_status` | `data.status` value not allowed | Use `active`, `pending`, `cancelled`, or `expired` |
| `action_failed` | General action failure | Check WooCommerce → Status → Logs (source: `subscription-pro-api`) |
| `rest_no_route` | API not enabled or wrong URL | Enable REST API in WPSubscription settings; check the endpoint path |

## Rate limits

The plugin itself does not throttle requests, but the WordPress host, WAF, or
CDN in front of `wp-json` commonly does. Treat any `429` as authoritative, honor
its `Retry-After` header when present, and self-throttle to roughly **1 request
per second per site** as a courtesy default.

## Versioning and deprecation

The API is versioned in the URL path (`/wp-json/wpsubscription/v1/`). Breaking
changes ship only under a new version namespace; `v1` request and response
shapes stay stable, and only additive changes (new actions, new optional
fields) land in `v1`. Deprecations are announced on the
[changelog](https://wpsubscription.co/changelog/) before removal.

## Revocation

The site owner revokes or rotates the key in **WP Admin → WPSubscription →
Settings**. Revocation takes effect immediately; later requests with the old key
fail.

## OAuth metadata

This service intentionally does **not** publish
`/.well-known/oauth-protected-resource` or
`/.well-known/oauth-authorization-server`, because it does not operate an OAuth
authorization server. The per-site API-key model above is the complete
authentication story.
