Requests with the user's token threw a bare ResourceException with a
message like "… for resource [Anikeen\Id\Resources\Order]" and no
error code, while app requests threw ApiException. Both throw
ApiException now: status (also getCode()), Anikeen ID's error code and
message, isNotFound(), isConflict(), isTemporary(). Failed lists throw
CollectionException, now a subclass of ApiException. A request without
a response (connection refused, timeout) throws ApiException with
status 0 instead of a Guzzle exception.
ApiException extends ResourceException, so existing catch blocks keep
working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- $user->appSubscriptions(): create (idempotency_key), find, all, checkout, revoke, pause,
resume, update against /v1/client/subscriptions with a client credentials token, so apps don't
depend on the user's login
- user id from the billable's anikeen_id (AnikeenId::useUserIdField())
- ApiException with status, stable error code, isNotFound/isConflict/isTemporary; network
errors no longer escape as raw Guzzle exceptions
- rejected app tokens are renewed once; asApp() never changes the shared client's token
- tests (testbench + Guzzle mock) and CI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>