/pom-ai-mcp/oauth/token: OAuth token exchange
Where an authorization code becomes an access token, and where expired tokens are renewed. The client handles it; you rarely see it unless something fails.
https://example.com/pom-ai-mcp/oauth/token
POST only. Anything else returns 405.
Two grant types
authorization_code exchanges a fresh code for tokens. The client sends the code, its client ID, the redirect URI it used, the resource, and the PKCE code_verifier.
refresh_token renews an expiring access token. The client sends the refresh token, its client ID and the resource.
Anything else returns unsupported_grant_type.
What comes back
An access token, a refresh token, token_type: Bearer and expires_in.
| Token | Lifetime |
|---|---|
| Access token | 1 hour |
| Refresh token | 30 days |
A client refreshes silently, so a working connection keeps working for up to thirty days of activity. After that, authorization is repeated.
What is checked
The exchange is strict, and each check corresponds to a failure you might see.
The resource must match. Compared against this site's MCP endpoint by scheme, host, port and path. A mismatch returns invalid_resource. This is the single most common OAuth failure, and it almost always means the client's configured URL differs from the endpoint — a www variant, a trailing path, or the wrong site of a multisite.
The client must exist. Unknown client IDs return invalid_client. After a Reset OAuth, previously registered clients produce this until they re-register.
The code must be valid. It must exist, be unused, be within its 10-minute life, belong to this client, match the redirect URI used, and match the resource. Otherwise invalid_grant.
PKCE must verify. The code_verifier must hash to the challenge sent at authorization. Otherwise invalid_grant.
A refresh token must be current. It must not be revoked or expired, must belong to this client, and must match the resource.
No client secret
The endpoint uses token_endpoint_auth_method: none. Clients are public and hold no secret, which is why PKCE carries the proof of possession.
A client trying to authenticate with a secret is not doing anything useful; the server does not expect one.
Rate limiting
5 requests per minute per IP, shared with the registration endpoint.
A client retrying a failing exchange in a loop will hit 429. When diagnosing, change one thing and try once rather than repeatedly — the rate limit will otherwise mask whether your fix worked.
Tokens are bound and hashed
Each token is bound to its client, the site's blog ID, the resource, the approving user and the approved scopes. Tokens are stored as hashes, never in plain text.
Consequences worth knowing:
- a token from one site never works on another;
- refreshing preserves the original scopes — it does not widen them;
- revoked and expired tokens are pruned automatically.
Requirements
- OAuth enabled, or the endpoint returns
404withoauth_disabled. - Pretty permalinks, since the path is a rewrite rule.
The localized form /es/pom-ai-mcp/oauth/token is also accepted where POM Translate prefixes URLs.
In the log
Successful exchanges are recorded with the grant type, client ID and the user the token acts as. A run of authorization_code entries for the same client suggests refresh is failing and the client is re-authorizing more often than it should.
When it fails
| Error | Fix |
|---|---|
invalid_resource |
Compare the client's URL against the MCP endpoint exactly |
invalid_client |
Re-register; usually follows a Reset OAuth |
invalid_grant |
The code expired, was reused, or the redirect URI differs — restart authorization |
unsupported_grant_type |
The client attempted a flow this server does not offer |
rate_limited |
Wait a minute; stop retrying in a loop |
oauth_disabled |
Enable OAuth in the MCP settings |
If the exchange succeeds but subsequent MCP calls still return 401, the token is fine and the Authorization header is being stripped in transit. The diagnostics in the MCP settings panel report whether that header arrives.