Skip to content

Users & profiles ​

The signed-in user ​

http
GET /api/v1/user
Authorization: Bearer USER_TOKEN
json
{
  "data": {
    "id": "01jexample…",
    "name": "Ada King",
    "username": "ada",
    "email": "ada@example.com",
    "email_verified_at": "2026-11-01T10:00:00.000000Z",
    "avatar": "https://id.anikeen.com/avatars/01jexample…",
    "locale": "en",
    "profile_version": 8,
    "billing_address_id": "01j…",
    "vat_rate": 19,
    "current_balance": 0,
    "current_charges": 12.5,
    "created_at": "…"
  }
}

The user as the user's own token sees them, including the VAT rate, balance and the open charges of the current month. Trusted apps also get role (admin or user). id is the user's Anikeen ID: store it to recognise the user.

  • avatar is a stable URL: the picture behind it changes when the user changes it.
  • profile_version grows with every change apps see (name, username, verified email, avatar). Compare it with the version of the user.updated webhook.

Scope: user:read (or user). A token without it gets only {"data": {"id": "…"}} once scopes are enforced (see Scopes): enough to check that the token still works, but no profile.

Linked social accounts ​

GET /api/v1/user/socialite-users lists the social logins linked to the account.

Changing the profile ​

With a token of the user carrying the scope user (* passes; user:read only reads), your app can change the user's name, username, email address and profile picture.

MethodPathBody
PATCH/api/v1/user/profile{name?, username?, email?} – only the fields sent change
POST/api/v1/user/profile/avatarmultipart: avatar (file), optional crop[x], crop[y], crop[size]
DELETE/api/v1/user/profile/avatar– removes the uploaded picture (back to the initials)
shell
curl -X PATCH https://id.anikeen.com/api/v1/user/profile \
  -H 'Authorization: Bearer USER_TOKEN' \
  -H 'Accept: application/json' -H 'Content-Type: application/json' \
  -d '{"name": "Ada King", "email": "ada.new@example.com"}'

Every 200 answer carries the user as GET /api/v1/user returns it, plus pending_email and pending_email_expires_at (both null without a pending change):

json
{"data": {"id": "01j…", "name": "Ada King", "email": "ada@example.com",
  "profile_version": 9, "…": "…",
  "pending_email": "ada.new@example.com", "pending_email_expires_at": "2026-11-16T10:00:00Z"}}
php
// with the SDK
$profile = $user->updateProfile(['name' => 'Ada King', 'email' => 'ada.new@example.com']);
$profile->pendingEmail();             // "ada.new@example.com" until the user confirms it

$user->uploadAvatar($request->file('avatar'));
$user->uploadAvatar($path, ['x' => 120, 'y' => 40, 'size' => 512]);
$user->removeAvatar();

Email addresses ​

A new email address never replaces the current one right away:

  • Anikeen ID mails a confirmation link to the new address and a notice to the current one.
  • The new address becomes the user's address only once they confirm it, signed in to Anikeen ID. An access token alone can't move the sign-in address.
  • Until then, the current address stays valid for sign-in and mails. user.updated follows the confirmation.
  • A new request replaces a pending one; a pending address is dropped after 7 days.
  • email is lower-cased.

Profile pictures ​

jpeg, png, webp or gif, at most 5 MB and 64 to 8000 pixels per side. crop is a square in pixels of the upright picture; without it the centre is used. The avatar URL stays the same when the picture changes.

Limits ​

30 requests a minute per app and user; new email addresses 5 an hour and picture uploads 10 an hour per user (429 too_many_requests with Retry-After). Requests with an address that is taken count too.

Errors ​

Errors are {"message", "error"}. Validation errors also carry errors (messages per field) and codes (a code per field):

json
{
  "message": "The username has already been taken.",
  "error": "username_taken",
  "errors": {"username": ["The username has already been taken."]},
  "codes": {"username": "username_taken"}
}
Statuserror
401unauthenticated
403missing_scope (no user scope), user_token_required (an app token: there is no user to change)
413avatar_too_large (the upload is larger than the server accepts)
422per field: name_invalid, username_taken, username_invalid, email_taken, email_invalid, avatar_invalid, avatar_too_large, crop_invalid (fields crop.x, crop.y, crop.size), else validation_failed
429too_many_requests

Usernames are 3–25 ASCII letters, digits, - and _, unique regardless of case.

Reading a user by ID (trusted apps) ​

Trusted apps can read the profile of a user connected to them with the app's own token, e.g. to catch up on a missed user.updated. No scope is needed.

http
GET /api/v1/client/users/{user}
Authorization: Bearer APP_TOKEN
json
{
  "data": {
    "id": "01jexample…",
    "name": "Ada King",
    "username": "ada",
    "email": "ada@example.com",
    "email_verified_at": "2026-11-01T10:00:00.000000Z",
    "avatar": "https://id.anikeen.com/avatars/01jexample…",
    "locale": "en",
    "profile_version": 8,
    "created_at": "…",
    "role": "user"
  }
}

The profile only: no balance, charges, payment method or security fields. The address is returned even when it isn't verified (email_verified_at is null then).

Statuserror
403client_not_allowed (not a trusted app with its own token)
404user_not_found (unknown, deleted or not connected to your app)
429too_many_requests (120 requests a minute per app)
php
$profile = app(\Anikeen\Id\AnikeenId::class)->findUserById($user->anikeen_id); // null: unknown, deleted or not connected

$profile?->name();
$profile?->email();          // returned even while unverified
$profile?->emailVerified();
$profile?->version();        // compare with the version of user.updated
$profile?->role();           // Role::ADMIN or Role::USER
$profile?->isAdmin();

Keeping profiles in sync ​

Subscribe to the opt-in webhooks user.updated and user.deleted. Keep the highest version you have seen per user and ignore older events. On user.deleted, delete or anonymise your copy of the account.

Anikeen ID developer documentation