# Me API

Current principal. 14 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /me

The authenticated user and principal

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (MeBody)
  - `createdAt` (string (date-time), required)
  - `email` (string, required)
  - `id` (string, required)
  - `instanceAdmin` (boolean, required): The first user: manages instance settings
  - `name` (string, required)
  - `principal` (PrincipalBody, required)
  - `workspaceId` (string, required)
Errors: 401 Unauthorized; 500 Internal Server Error

## PATCH /me

Update the signed-in user's profile

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): UpdateMeInputBody
  - `name` (string, required)

Response 200: OK (MeBody)
  - `createdAt` (string (date-time), required)
  - `email` (string, required)
  - `id` (string, required)
  - `instanceAdmin` (boolean, required): The first user: manages instance settings
  - `name` (string, required)
  - `principal` (PrincipalBody, required)
  - `workspaceId` (string, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /me/2fa/disable

Turn 2FA off (needs a code, and the password when the account has one)

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): TotpDisableInputBody
  - `code` (string, required): A current authenticator code or a recovery code
  - `password` (string): Required when the account has a password

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## POST /me/2fa/enable

Confirm the authenticator and turn 2FA on

Returns the 10 single-use recovery codes once. This session counts as second-factor verified afterwards.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): TotpEnableInputBody
  - `code` (string, required)

Response 200: OK (RecoveryCodesOutputBody)
  - `recoveryCodes` (string[] | null, required): Single-use codes; shown once
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## POST /me/2fa/recovery-codes

Replace the recovery codes (needs a current code)

Invalidates every previous code and returns 10 new ones, shown once.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): RegenerateRecoveryCodesInputBody
  - `code` (string): A current authenticator code or an unused recovery code
  - `password` (string): Instead of a code, for an account that uses passkeys and no authenticator app

Response 200: OK (RecoveryCodesOutputBody)
  - `recoveryCodes` (string[] | null, required): Single-use codes; shown once
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## POST /me/2fa/setup

Generate a TOTP secret to add to an authenticator app

Replaces any unconfirmed secret. Confirm it with POST /me/2fa/enable. Returns 409 when 2FA is already on.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (TotpSetupOutputBody)
  - `secret` (string, required): Base32 secret for manual entry
  - `uri` (string, required): otpauth:// URI for authenticator apps
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 500 Internal Server Error

## DELETE /me/identities/{id}

Unlink a single-sign-on account

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `id` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## GET /me/passkeys

The signed-in user's passkeys

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (PasskeysOutputBody)
  - `passkeys` (PasskeyBody[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error

## POST /me/passkeys

Finish adding a passkey

Counts as a second factor for the team 2FA requirement. When the account has no unused recovery code left, a fresh set is issued and returned once.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): PasskeyRegisterFinishInputBody
  - `credential` (any, required): The RegistrationResponseJSON of the authenticator (PublicKeyCredential.toJSON())
  - `name` (string, required): What to call this passkey, for example 'MacBook' or 'YubiKey'

Response 201: Created (PasskeyRegisterFinishOutputBody)
  - `passkey` (PasskeyBody, required)
  - `recoveryCodes` (string[] | null): Issued when the account had no unused recovery code left; single use, shown once
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## PATCH /me/passkeys/{id}

Rename a passkey

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `id` (path, string, required)

Request body (application/json, required): PasskeyRenameInputBody
  - `name` (string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /me/passkeys/{id}

Remove a passkey (needs the password when the account has one)

Fails with 409 `last_sign_in_method` when it is the account's only way to sign in.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `id` (path, string, required)

Request body (application/json, required): PasskeyDeleteInputBody
  - `password` (string): Required when the account has a password

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict (`last_sign_in_method`); 422 Unprocessable Entity; 500 Internal Server Error

## POST /me/passkeys/options

Start adding a passkey

Returns the WebAuthn creation options for `navigator.credentials.create`; the answer goes to `POST /me/passkeys`. Needs the password again when the account has one. Passkeys are discoverable credentials with user verification required.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): PasskeyRegisterStartInputBody
  - `password` (string): Required when the account has a password

Response 200: OK (any)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## POST /me/password

Change the signed-in user's password

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): ChangePasswordInputBody
  - `currentPassword` (string, required)
  - `newPassword` (string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## GET /me/security

Two-factor and linked-account status of the signed-in user

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (SecurityBody)
  - `enrollmentNeeded` (boolean, required): The current team requires 2FA and this account has not enrolled; everything else is blocked until it does
  - `hasPassword` (boolean, required)
  - `identities` (IdentityBody[] | null, required)
  - `isInstanceAdmin` (boolean, required)
  - `passkeys` (PasskeyBody[] | null, required)
  - `passkeysUnavailable` (string): Why passkeys cannot be used at this address (it needs HTTPS and a domain name); absent when they can
  - `recoveryCodesLeft` (integer (int64), required)
  - `requiredByTeams` (RequiredTeamBody[] | null, required)
  - `sessionMfa` (boolean, required): This session proved a second factor
  - `totpEnabled` (boolean, required)
  - `totpPending` (boolean, required): A secret was generated but not confirmed yet
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error
