Skip to content
AquadactylAquadactyl
API referenceClient API

Account, security and API keys

18 source-verified client API operations in Aquadactyl.

Generated from Aquadactyl 2bfd6ae2a30c. Read authentication and conventions first.

GET /api/client/account

Calls AccountController::index.

Access: Client API key or authenticated panel session.

Source: Route · Controller

Response

Success: 200. Response attributes: Client AccountTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

return $this->fractal->item($request->user())
    ->transformWith($this->getTransformer(AccountTransformer::class))
    ->toArray();

Example request

curl --get "${PANEL_URL}/api/client/account" \
  --header "Authorization: Bearer ${CLIENT_API_KEY}" \
  --header "Accept: application/json"

GET /api/client/account/two-factor

Returns two-factor token credentials that allow a user to configure it on their account. If two-factor is already enabled this endpoint will return a 400 error.

Access: Client API key or authenticated panel session.

Source: Route · Controller

Response

Success: 200.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

if ($request->user()->use_totp) {
    throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.');
}

return new JsonResponse([
    'data' => $this->setupService->handle($request->user()),
]);

Example request

curl --get "${PANEL_URL}/api/client/account/two-factor" \
  --header "Authorization: Bearer ${CLIENT_API_KEY}" \
  --header "Accept: application/json"

POST /api/client/account/two-factor

Updates a user's account to have two-factor enabled.

Access: Client API key or authenticated panel session.

Source: Route · Controller

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
code['required', 'string', 'size:6']
password['required', 'string']

Response

Success: 200.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$validator = $this->validation->make($request->all(), [
    'code' => ['required', 'string', 'size:6'],
    'password' => ['required', 'string'],
]);

$data = $validator->validate();
if (!password_verify($data['password'], $request->user()->password)) {
    throw new BadRequestHttpException('The password provided was not valid.');
}

$tokens = $this->toggleTwoFactorService->handle($request->user(), $data['code'], true);

Activity::event('user:two-factor.create')->log();

return new JsonResponse([
    'object' => 'recovery_tokens',
    'attributes' => [
        'tokens' => $tokens,
    ],
]);

POST /api/client/account/two-factor/disable

Disables two-factor authentication on an account if the password provided is valid.

Access: Client API key or authenticated panel session.

Source: Route · Controller

Other controller inputs: password.

Response

Success: 204 (empty body).

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

if (!password_verify($request->input('password') ?? '', $request->user()->password)) {
    throw new BadRequestHttpException('The password provided was not valid.');
}

$user = $request->user();

$user->update([
    'totp_authenticated_at' => Carbon::now(),
    'use_totp' => false,
]);

Activity::event('user:two-factor.delete')->log();

return new JsonResponse([], Response::HTTP_NO_CONTENT);

POST /api/client/account/two-factor/regenerate-tokens

Regenerates recovery tokens for an account if two-factor is enabled and the password provided is valid.

Access: Client API key or authenticated panel session.

Source: Route · Controller

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
password['required', 'string']

Response

Success: 200.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

if (!$request->user()->use_totp) {
    throw new BadRequestHttpException('Two-factor authentication is not enabled on this account.');
}

$validator = $this->validation->make($request->all(), [
    'password' => ['required', 'string'],
]);

$data = $validator->validate();
if (!password_verify($data['password'], $request->user()->password)) {
    throw new BadRequestHttpException('The password provided was not valid.');
}

$tokens = $this->recoveryTokenService->handle($request->user());

Activity::event('user:two-factor.regenerate-tokens')->log();

return new JsonResponse([
    'object' => 'recovery_tokens',
    'attributes' => [
        'tokens' => $tokens,
    ],
]);

PUT /api/client/account/email

Update the authenticated user's email address.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Model constraints: User.php (including runtime rules and uniqueness checks for updates).

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
email['required', 'email:strict', 'between:1,191', 'unique:users,email']
Additional authorization checks
if (!parent::authorize()) {
    return false;
}

$hasher = Container::getInstance()->make(Hasher::class);

if (!$hasher->check($this->input('password'), $this->user()->password)) {
    throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}

return true;

Response

Success: 204 (empty body).

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$user = $request->user();

if (RateLimiter::tooManyAttempts($key = "user:update-email:{$user->uuid}", 3)) {
    throw new TooManyRequestsHttpException(message: 'Your email address has been changed too many times today. Please try again later.');
}

$original = $user->email;
if (mb_strtolower($original) !== mb_strtolower($request->validated('email'))) {
    RateLimiter::hit($key, self::EMAIL_UPDATE_THROTTLE);

    $this->updateService->handle($user, $request->validated());

    Activity::event('user:account.email-changed')
        ->property(['old' => $original, 'new' => $request->validated('email')])
        ->log();
}

return new JsonResponse([], Response::HTTP_NO_CONTENT);

PUT /api/client/account/password

Update the authenticated user's password. All existing sessions will be logged out immediately.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
password['required', 'string', 'confirmed', 'min:8']
Additional authorization checks
if (!parent::authorize()) {
    return false;
}

$hasher = Container::getInstance()->make(Hasher::class);

if (!$hasher->check($this->input('current_password'), $this->user()->password)) {
    throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}

return true;

Response

Success: 204 (empty body).

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$user = Activity::event('user:account.password-changed')->transaction(function () use ($request) {
    return $this->updateService->handle($request->user(), $request->validated());
});

$guard = $this->manager->guard();

$guard->setUser($user);

if (method_exists($guard, 'logoutOtherDevices')) { // @phpstan-ignore function.alreadyNarrowedType
    $guard->logoutOtherDevices($request->input('password'));
}

return new JsonResponse([], Response::HTTP_NO_CONTENT);

POST /api/client/account/avatar

Calls AccountAvatarController::store.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
avatar['bail', 'required', 'file', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048', 'dimensions:min_width=1,min_height=1,max_width=4096,max_height=4096']
Additional authorization checks
return config('aquadactyl.features.custom_profile_pictures', true) && parent::authorize();

Response

Success: 200. Response attributes: Client AccountTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$user = $service->upload($request->user(), $request->file('avatar'));
Activity::event('user:account.avatar-updated')->log();

return $this->fractal->item($user)
    ->transformWith($this->getTransformer(AccountTransformer::class))
    ->toArray();

DELETE /api/client/account/avatar

Calls AccountAvatarController::destroy.

Access: Client API key or authenticated panel session.

Source: Route · Controller

Response

Success: 200. Response attributes: Client AccountTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$user = $service->remove($request->user());
Activity::event('user:account.avatar-removed')->log();

return $this->fractal->item($user)
    ->transformWith($this->getTransformer(AccountTransformer::class))
    ->toArray();

PUT /api/client/account/privacy

Calls AccountPrivacyController::update.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
blur_sensitive_data['required', 'boolean']
Additional authorization checks
return config('aquadactyl.features.privacy_mode', true) && parent::authorize();

Response

Success: 200. Response attributes: Client AccountTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$user = $request->user();
$enabled = $request->boolean('blur_sensitive_data');
if ($user->blur_sensitive_data !== $enabled) {
    $user->forceFill(['blur_sensitive_data' => $enabled])->save();
    Activity::event('user:account.privacy-updated')->subject($user)
        ->property('blur_sensitive_data', $enabled)->log();
}

return $this->fractal->item($user)
    ->transformWith($this->getTransformer(AccountTransformer::class))
    ->toArray();

PUT /api/client/account/language

Calls AccountLanguageController::update.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
language[ 'required', 'string', Rule::in([ LanguageService::AUTOMATIC, ...array_keys(app(LanguageService::class)->available()), ]), ]

Response

Success: 200.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$user = $request->user();
$user->forceFill(['language' => $request->validated('language')])->save();

return [
    'language' => $user->language,
    'locale' => $languages->forRequest($request, $user->language),
];

GET /api/client/account/activity

Returns a paginated set of the user's activity logs.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Query fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
page['sometimes', 'integer', 'min:1']
per_page['sometimes', 'integer', 'between:1,100']
filter.event['sometimes', 'nullable', 'string', 'max:255']
filter.event_exact['sometimes', 'nullable', 'string', 'max:255']
filter.ip['sometimes', 'nullable', 'ip']
filter.period['sometimes', Rule::in(['24h', '7d', '30d', '90d'])]
filter.date['sometimes', 'nullable', 'date_format:Y-m-d']
filter.source['sometimes', Rule::in(['web', 'api', 'sftp', 'system'])]

List query options: filters declared in ...$filters->allowedFilters($request->user()); sorts 'timestamp'. Use filter[name]=value and sort=-id only with supported fields.

Response

Success: 200. Response attributes: Client ActivityLogTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$query = $request->user()->activity()
    ->whereNotIn('activity_logs.event', ActivityLog::DISABLED_EVENTS);
$events = $filters->availableEvents($query->getQuery());

$activity = QueryBuilder::for($query)
    ->with('actor')
    ->allowedFilters(...$filters->allowedFilters($request->user()))
    ->allowedSorts('timestamp')
    ->defaultSort('-timestamp')
    ->orderBy('activity_logs.id', 'desc')
    ->paginate(min($request->query('per_page', 25), 100))
    ->appends($request->query());

return $this->fractal->collection($activity)
    ->transformWith($this->getTransformer(ActivityLogTransformer::class))
    ->addMeta(['available_events' => $events])
    ->toArray();

Example request

curl --get "${PANEL_URL}/api/client/account/activity" \
  --header "Authorization: Bearer ${CLIENT_API_KEY}" \
  --header "Accept: application/json"

GET /api/client/account/api-keys

Returns all the API keys that exist for the given client.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Validation is inherited from ApplicationApiRequest.php.

Response

Success: 200. Response attributes: Client ApiKeyTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

return $this->fractal->collection($request->user()->apiKeys)
    ->transformWith($this->getTransformer(ApiKeyTransformer::class))
    ->toArray();

Example request

curl --get "${PANEL_URL}/api/client/account/api-keys" \
  --header "Authorization: Bearer ${CLIENT_API_KEY}" \
  --header "Accept: application/json"

POST /api/client/account/api-keys

Store a new API key for a user's account.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Model constraints: ApiKey.php (including runtime rules and uniqueness checks for updates).

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
description['required', 'nullable', 'string', 'max:500']
allowed_ips[...['nullable', 'array'], 'max:50']
allowed_ips.*'string'
Conditional validation
$validator->after(function (Validator $validator) {
    if (!is_array($ips = $this->input('allowed_ips'))) {
        return;
    }

    foreach ($ips as $index => $ip) {
        $valid = is_string($ip) && Factory::parseRangeString($ip) !== null;
        $validator->errors()->addIf(!$valid, "allowed_ips.{$index}", '"' . $ip . '" is not a valid IP address or CIDR range.');
    }
});

Response

Success: 200. Response attributes: Client ApiKeyTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$token = DB::transaction(function () use ($request) {
    if ($request->user()->apiKeys()->lockForUpdate()->count() >= 25) {
        throw new DisplayException('You have reached the account limit for number of API keys.');
    }

    return $request->user()->createToken(
        $request->input('description'),
        $request->input('allowed_ips')
    );
});

Activity::event('user:api-key.create')
    ->subject($token->accessToken)
    ->property('identifier', $token->accessToken->identifier)
    ->log();

return $this->fractal->item($token->accessToken)
    ->transformWith($this->getTransformer(ApiKeyTransformer::class))
    ->addMeta(['secret_token' => $token->plainTextToken])
    ->toArray();

DELETE /api/client/account/api-keys/{identifier}

Deletes a given API key.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Validation is inherited from ApplicationApiRequest.php.

Path parameters: identifier. See identifiers.

Response

Success: 204 (empty body).

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$key = $request->user()->apiKeys()
    ->where('key_type', ApiKey::TYPE_ACCOUNT)
    ->where('identifier', $identifier)
    ->firstOrFail();

Activity::event('user:api-key.delete')
    ->property('identifier', $key->identifier)
    ->log();

$key->delete();

return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);

GET /api/client/account/ssh-keys

Returns all the SSH keys that have been configured for the logged-in user account.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Validation is inherited from ApplicationApiRequest.php.

Response

Success: 200. Response attributes: Client UserSSHKeyTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

return $this->fractal->collection($request->user()->sshKeys)
    ->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
    ->toArray();

Example request

curl --get "${PANEL_URL}/api/client/account/ssh-keys" \
  --header "Authorization: Bearer ${CLIENT_API_KEY}" \
  --header "Accept: application/json"

POST /api/client/account/ssh-keys

Stores a new SSH key for the authenticated user's account.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
name['required', 'string']
public_key['required', 'string', 'max:' . self::PUBLIC_KEY_MAX_LENGTH]
Conditional validation
$validator->after(function () {
    $publicKey = $this->input('public_key');
    if (!is_string($publicKey)) {
        return;
    }

    $publicKey = trim($publicKey);
    if (!UserSSHKey::isSupportedPublicKeyMaterial($publicKey)) {
        $this->validator->errors()->add('public_key', 'The public key provided is not valid.');

        return;
    }

    try {
        $this->key = PublicKeyLoader::loadPublicKey($publicKey);
    } catch (NoKeyLoadedException $exception) {
        $this->validator->errors()->add('public_key', 'The public key provided is not valid.');

        return;
    }

    if ($this->key instanceof DSA) {
        $this->validator->errors()->add('public_key', 'DSA keys are not supported.');
    }

    if ($this->key instanceof RSA && $this->key->getLength() < 2048) {
        $this->validator->errors()->add('public_key', 'RSA keys must be at least 2048 bytes in length.');
    }

    $fingerprint = $this->key->getFingerprint('sha256');
    if ($this->user()->sshKeys()->where('fingerprint', $fingerprint)->exists()) {
        $this->validator->errors()->add('public_key', 'The public key provided already exists on your account.');
    }
});

Response

Success: 200. Response attributes: Client UserSSHKeyTransformer. Collections use the list envelope described in conventions.

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$model = $request->user()->sshKeys()->create([
    'name' => $request->input('name'),
    'public_key' => $request->getPublicKey(),
    'fingerprint' => $request->getKeyFingerprint(),
]);

Activity::event('user:ssh-key.create')
    ->subject($model)
    ->property('fingerprint', $request->getKeyFingerprint())
    ->log();

return $this->fractal->item($model)
    ->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
    ->toArray();

POST /api/client/account/ssh-keys/remove

Deletes an SSH key from the user's account.

Access: Client API key or authenticated panel session.

Source: Route · Controller · Request validation

Validation is inherited from ApplicationApiRequest.php.

Input fields

Rules use Laravel validation syntax. Dotted keys describe nested objects; * describes each array member. Model-derived rules are shown as their source expressions; model methods can add runtime constraints.

FieldValidation
fingerprint['required', 'string']

Response

Success: 204 (empty body).

Response construction and endpoint behavior

This source excerpt includes conditional responses, metadata, pagination, and service calls for this operation.

$this->validate($request, ['fingerprint' => ['required', 'string']]);

$key = $request->user()->sshKeys()
    ->where('fingerprint', $request->input('fingerprint'))
    ->first();

if (!is_null($key)) {
    $key->delete();

    Activity::event('user:ssh-key.delete')
        ->subject($key)
        ->property('fingerprint', $key->fingerprint)
        ->log();
}

return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);